(add-k8s-ioc)=
# Deploy an IOC with Helm
:::{warning}
**DLS users:** beamline and accelerator IOCs are deployed through the internal
developer guide at , not these
public cluster steps. Follow along on your own test cluster only.
:::
This is your **first deployment of your own IOC onto the cluster**, using plain
Helm. In {any}`setup-k8s-beamline` you created the services repo `t02-services`
and deployed its shared services with the `ec` **K8S backend** (which runs
`helm upgrade --install` under the hood). Now you add **your own** IOC — a
simulated area detector, much like the one built in {any}`create_ioc`, but
defined for a Helm {any}`services repo ` — and deploy it the same
way, with `ec deploy`. Substitute your own names throughout.
By the end you will have a new IOC instance folder in your services repo,
committed, pushed and running on the cluster. Later, {any}`deploy-argocd` puts
this same repo under GitOps control with ArgoCD.
## Add a new instance folder
An IOC instance is a folder under `services/`. Its folder name is the IOC name,
and it holds two things:
| Item | Purpose |
|---|---|
| `values.yaml` | Helm values for the `ioc-instance` chart — chiefly *which container image* to run. |
| `config/` | The IOC configuration, mounted into the container as a Kubernetes ConfigMap. Normally a single `ibek` file, `ioc.yaml`. |
Your services repo ships a `.ioc_template` skeleton to copy. From the repo root:
```bash
cd t02-services
cp -r services/.ioc_template services/bl02t-ea-cam-01
code .
```
:::{note}
**DLS users:** `module load vscode` first, then `code .`.
:::
The skeleton's `Chart.yaml` and `templates/` are symlinks into the shared
`.helm-shared/` chart, so every instance reuses the same `ioc-instance` chart —
you never edit them.
## Set the image — values.yaml
Edit `services/bl02t-ea-cam-01/values.yaml` and replace the placeholder image
with the SimDetector Generic IOC:
```yaml
# yaml-language-server: $schema=../../.helm-shared/values.schema.json
ioc-instance:
image: ghcr.io/epics-containers/ioc-adsimdetector-runtime:2.11ec3
```
`ioc-instance:` is the key the chart imports its values under. Shared defaults
(such as `hostNetwork: true`) come from the repo-root `services/values.yaml`, so
a per-instance file normally only overrides what differs — usually just `image`.
## Configure the IOC — config/ioc.yaml
An `ibek` `ioc.yaml` is a list of `entities`; each instantiates a model
contributed by a support module. The Generic IOC bakes in the support it can
instantiate, so nothing extra is downloaded here. Open
`services/bl02t-ea-cam-01/config/ioc.yaml` and replace its body:
```yaml
# yaml-language-server: $schema=../ioc.schema.json
ioc_name: "{{ _global.get_env('IOC_NAME') }}"
description: An IOC that simulates an area detector
entities:
- type: epics.EpicsEnvSet
name: EPICS_TZ
value: GMT0BST
- type: devIocStats.iocAdminSoft
IOC: "{{ ioc_name | upper }}"
- type: ADSimDetector.simDetector
PORT: DET.DET
P: BL02T-EA-CAM-01
R: ":DET:"
- type: ADCore.NDStdArrays
PORT: DET.ARR
P: BL02T-EA-CAM-01
R: ":ARR:"
NDARRAY_PORT: DET.DET
TYPE: Int8
FTVL: CHAR
NELEMENTS: 1048576
```
This makes a simulation detector with PV prefix `BL02T-EA-CAM-01:DET:` and a
Standard Arrays plugin that publishes its image over Channel Access.
:::{note}
YAML indentation is significant: each `- type:` starts a new entity, and a value
that begins with `:` must be quoted (as in `R: ":DET:"`).
:::
The schema line points at `../ioc.schema.json`. Generate it once so VSCode (with
the Red Hat YAML extension) offers completion and validation for exactly the
entities this image provides:
```bash
ibek pattern schema services/bl02t-ea-cam-01
```
`ibek` fetches the published schema for your pinned image and writes
`services/bl02t-ea-cam-01/ioc.schema.json`. To learn where these entity models
come from and build your own, see {any}`generic_ioc`.
## Vendor extra runtime support (optional)
Some IOCs need runtime inputs that are **not** compiled into the image —
StreamDevice protocol files, EPICS DB templates, and the `ibek` models that
describe them. Vendor them into the instance's `config/` from a central pattern
library:
```bash
ibek pattern add :@ services/bl02t-ea-cam-01
```
This copies the files into `config/`, records each one with a pinned version and
`sha256` hash in `services/bl02t-ea-cam-01/runtime-lock.yaml`, and refreshes
`ioc.schema.json` so the new entities validate too. Because `config/` is mounted
as the ConfigMap, keep its total content under 1 MiB. `ibek pattern check`
verifies the vendored files still match the lock. The simulated detector above
needs none of this, so you can skip the section.
## Deploy it
Make sure your environment is configured for the **K8S backend** — source the
repo's `environment.sh` (see {any}`setup-k8s-beamline`), which sets
`EC_CLI_BACKEND=K8S`. Then commit your new instance and push it to the services
repo that `ec` deploys from (`EC_SERVICES_REPO`):
```bash
git add services/bl02t-ea-cam-01
git commit -m "Add bl02t-ea-cam-01 IOC"
git push
```
Now deploy it (`-v` prints the underlying helm/kubectl commands):
```bash
ec -v deploy bl02t-ea-cam-01 main
```
Under the K8S backend `ec deploy` does **not** go through ArgoCD or a deployment
repo. Instead it:
- clones `t02-services` at the requested revision (`main` here);
- packages `services/bl02t-ea-cam-01` as a Helm chart, applying the shared
`services/values.yaml` and the instance `values.yaml`;
- runs `helm upgrade --install bl02t-ea-cam-01 ... --namespace t02-beamline`
directly against the cluster.
Use a git tag instead of `main` (for example `ec deploy bl02t-ea-cam-01 2026.7.1`)
to pin a specific version, exactly as you tagged and deployed the shared services
in {any}`setup-k8s-beamline`.
Confirm it is running, then inspect it:
```bash
ec ps # list services in your namespace
ec logs bl02t-ea-cam-01 # stream the IOC's container logs
```
```text
name version ready deployed
t02-epics-pvcs 2026.7.1 True 2026-07-01T09:10:00Z
t02-epics-opis 2026.7.1 True 2026-07-01T09:11:00Z
bl02t-ea-cam-01 main True 2026-07-01T09:20:00Z
```
Pause and resume the IOC without removing it:
```bash
ec stop bl02t-ea-cam-01
ec start bl02t-ea-cam-01
```
See {any}`edge-containers-cli` for the full `ec` command and environment-variable
reference.
## Next steps
- {any}`setup-argocd` — install ArgoCD, the next step towards GitOps deployment.
- {any}`deploy-argocd` — put this same repo under GitOps control with ArgoCD (the
production continuous-deployment flow), switching the `ec` backend from K8S to
ARGOCD.
- {any}`helm` — the pure-Helm reference for what `ec deploy` runs under the K8S
backend.
- {any}`generic_ioc` — build your own Generic IOC image when no published one
fits.