Deploy IOCs with ArgoCD#
This tutorial walks you through the production continuous deployment (CD)
path for epics-containers: you create a deployment repository from a
template, bootstrap a single ArgoCD root Application, and then deploy an IOC
with one ec deploy command. From that point on, ArgoCD keeps your cluster in
sync with what is recorded in git.
By the end you will have:
a deployment repo (
t01-deployment) generated fromdeployment-template-argocd;a root ArgoCD Application (
t01) that owns a set of child Applications;the IOC
bl01t-ea-fastcs-01running on your cluster, with its desired version recorded in git.
The worked example uses the domain t01 (beamline bl01t), the
namespace t01-beamline, and the public services repository
t01-services. Substitute
your own names throughout.
Note
This is the GitOps deployment model. If you only want to understand how it works (the two-repository split, app-of-apps, auto-sync) without running the commands, read How ArgoCD Deploys Your IOCs first and come back here.
Prerequisites#
This tutorial assumes the pieces below are already in place. It deliberately does not repeat the installation steps.
A Kubernetes cluster with ArgoCD installed, and the
argocdCLI installed and logged in. The Setup a Kubernetes Cluster tutorial installs ArgoCD into a cluster, installs theargocdCLI, and shows how to reach the ArgoCD web UI (for a self-hosted install, typically by port-forwarding it tohttps://localhost:8081/). Follow that tutorial first if you have not already.A workstation with
ec,copier,gitandkubectlavailable. See Set up a Developer Workstation for the workstation setup.ecmust already be installed on your workstation; the generatedenvironment.shonly checks thatecis present and enables its shell completion (see Configure your environment below) — it does not installecfor you.A services repository to deploy from. A services repo holds the Helm charts and per-service
values.yamlfiles that define each IOC or service (see services repository). This tutorial uses the publict01-services, so you do not need to create one. If you want to build your own beamline’s services repo, see Create a New Kubernetes Beamline and Create a Beamline Repository.A namespace and a matching ArgoCD project for your domain. ArgoCD Applications are authorised per-namespace and per-project. Create the namespace your IOCs will run in, and an ArgoCD project that is allowed to deploy into it:
kubectl create namespace t01-beamline argocd proj create t01-beamline \ -d https://kubernetes.default.svc,t01-beamline \ -s "*"
Note
The deployment template sets the Application
project:field to your namespace name (thecluster_namespaceyou givecopierbelow). That is why the ArgoCD project here is namedt01-beamline, matching the namespace. The-dflag whitelists the destinationcluster,namespace;-s "*"permits any source repo. Adjust the cluster URL/name if your IOCs run on a different cluster from the one hosting ArgoCD.
Scaffold a deployment repo#
The deployment repo is generated from the public
deployment-template-argocd
template using copier:
copier copy https://github.com/epics-containers/deployment-template-argocd t01-deployment
Note
If copier is not installed on your workstation you can run it on demand with
uvx copier copy https://github.com/epics-containers/deployment-template-argocd t01-deployment.
copier asks a series of questions (defined in the template’s copier.yml).
Answer them as follows for the worked example:
Prompt |
Meaning |
Worked-example answer |
|---|---|---|
|
Short name for this collection of IOCs/services. Becomes the root Application name. |
|
|
One-line repo description. |
(accept default) |
|
DNS name of your ArgoCD server, used by |
your ArgoCD server, e.g. |
|
The cluster where ArgoCD creates the Application objects. |
|
|
The cluster where the IOCs run ( |
|
|
The namespace where IOCs run, and the ArgoCD project. |
|
|
Where this repo will be hosted. |
|
|
The GitHub account/org that will own the repo. |
your GitHub account or org |
|
URL of this deployment repo. |
(accept default — |
|
URL of the services repo to track. |
|
|
Initial branch or tag of the services repo to track. |
|
|
Central log server URL (optional). |
|
Warning
The template defaults are placeholders and will not work unchanged. The two
URLs that matter most are deployment_repo (must point at the repo you are
about to push) and services_repo (must point at
https://github.com/epics-containers/t01-services). Set both correctly.
Now create the git repository, commit the generated files, and push to the
remote you named in deployment_repo:
git -C t01-deployment init
git -C t01-deployment add .
git -C t01-deployment commit -m "Initial deployment repo from template"
git -C t01-deployment branch -M main
git -C t01-deployment remote add origin https://github.com/<org>/t01-deployment
git -C t01-deployment push -u origin main
Important
ArgoCD must be able to read this repo. The simplest option is a public
repo. If you use a private repo, register the credentials with ArgoCD first —
otherwise the root Application reports
ComparisonError: authentication required.
Tour the generated repo#
Before bootstrapping, take a moment to look at what the template produced. The deployment repo is deliberately tiny — it records only which services run and at what version; the service content lives in the services repo.
Path |
Role |
|---|---|
|
The ArgoCD root Application (“app of apps”). |
|
The control surface — the only file you (or CI) normally edit. Declares the project, destination, the services-repo source, and the |
|
A Helm chart whose only dependency is the |
|
A one-line template that expands |
|
Sourced to set the |
The root apps.yaml looks like this (genericized):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: t01
namespace: t01-beamline
finalizers:
- resources-finalizer.argocd.argoproj.io/background
- resources-finalizer.argocd.argoproj.io/foreground
spec:
project: t01-beamline
destination:
name: in-cluster
namespace: t01-beamline
source:
path: apps
repoURL: https://github.com/<org>/t01-deployment # THIS (deployment) repo
targetRevision: main
helm:
version: v3
syncPolicy:
automated:
prune: true
selfHeal: true
And apps/values.yaml — the control surface:
project: t01-beamline
destination:
name: in-cluster
namespace: t01-beamline
source:
repoURL: https://github.com/epics-containers/t01-services # the SERVICES repo
targetRevision: main
services:
t01-epics-pvcs:
t01-epics-opis:
t01-epics-gateways:
Note
Note that source.repoURL here points at the services repo, not the
deployment repo. The root Application’s source.repoURL (in apps.yaml) points
at the deployment repo; every child Application sources its Helm chart from
services/<service> in the services repo. The template seeds three child
services: t01-epics-pvcs (shared storage), t01-epics-opis (auto-generated
OPIs) and t01-epics-gateways (a Channel Access gateway). A bare entry with no
value — like t01-epics-pvcs: — inherits all the defaults above.
For the full model behind these files — the two-repository split, the
argocd-apps library chart, and how one map becomes many Applications — see
How ArgoCD Deploys Your IOCs.
Configure your environment#
The generated environment.sh wires up your shell. Source it from the directory
that contains the deployment repo:
source ./t01-deployment/environment.sh
This sets the EC_* environment variables, enables ec shell completion, and
logs you into ArgoCD. The variables it exports are:
export EC_CLI_BACKEND="ARGOCD" # use the ArgoCD continuous-deployment backend
export EC_TARGET=t01-beamline/t01 # <namespace>/<root-app-name>
export EC_SERVICES_REPO=https://github.com/epics-containers/t01-services
export EC_LOG_URL= # central log server URL (empty — logging_url was Skipped)
EC_TARGET is the namespace of your Application objects followed by the root
Application name (t01-beamline/t01); EC_SERVICES_REPO is the services repo
that ec deploy validates versions against; EC_CLI_BACKEND=ARGOCD selects the
ArgoCD backend (see edge-containers-cli).
Note
Site setup varies. The template’s environment.sh ends with a generic
argocd login <server> --grpc-web --sso. Adapt this line to your own server and
auth method. For a self-hosted install reached via port-forward, that is
typically:
argocd login localhost:8081
logging in as admin with the password from your ArgoCD install (see
Setup a Kubernetes Cluster).
Check that the CLI is configured. Until you bootstrap the root Application
(next section) the target does not exist yet, so ec ps reports
Target 't01-beamline/t01' not found:
ec ps
That error is expected at this stage; it confirms ec is configured and talking
to ArgoCD with the right target. Once the root Application exists, the same
command lists your services.
Bootstrap the root Application#
This is the single manual step. From the directory that contains the deployment
repo, create the root Application from t01-deployment/apps.yaml:
argocd app create --file t01-deployment/apps.yaml
ArgoCD now creates the root Application t01, which in turn creates one child
Application per entry in apps/values.yaml. Within a moment you should see the
root plus its three seeded children:
argocd app list --app-namespace t01-beamline
NAME SYNC STATUS HEALTH
t01-beamline/t01 Synced Healthy
t01-beamline/t01-epics-pvcs Synced Healthy
t01-beamline/t01-epics-opis Synced Healthy
t01-beamline/t01-epics-gateways Synced Healthy
Note
kubectl apply -f apps.yaml is the equivalent Kubernetes-native form
(apps.yaml is a valid Application resource). It works, but relies on your
direct cluster RBAC rather than ArgoCD’s project authorisation — prefer the
argocd CLI form shown above.
Watch it sync in the ArgoCD web UI#
Open your ArgoCD web UI (for a port-forwarded install, https://localhost:8081/)
and filter the Applications view by project t01-beamline. You will see a card
for the root t01 and one for each child. As ArgoCD reconciles, the cards turn
green (Synced / Healthy). Click a card to drill into the individual
Kubernetes resources (StatefulSets, Services, ConfigMaps) it manages — this is
the quickest way to diagnose a service that will not start.
Deploy a service#
Now deploy an IOC. The service bl01t-ea-fastcs-01 exists in the
t01-services repo, so you can deploy it directly:
ec deploy bl01t-ea-fastcs-01 main
Use a git tag instead of main (for example ec deploy bl01t-ea-fastcs-01 2024.12.1)
to pin a specific version.
Here is exactly what happened, and what did not:
ecchecked thatservices/bl01t-ea-fastcs-01exists int01-servicesat the requested revision.ecthen committed and pushed an entry underservices.bl01t-ea-fastcs-01in the deployment repo’sapps/values.yaml, recording the desired version. This commit is the source of truth.ecranargocd app get --refreshto ask ArgoCD to re-read git immediately (otherwise ArgoCD would notice on its next poll — every 3 minutes by default, or instantly if you have configured a git webhook).ArgoCD’s auto-sync (the
automated/prune/selfHealpolicy) then reconciles the cluster to match git.
Important
ec deploy does not run argocd app sync. Under the ArgoCD backend it only
records desired state in git and refreshes ArgoCD; the actual reconciliation
is performed by ArgoCD’s auto-sync. To force an immediate sync manually (for
example if auto-sync has been paused), use the Sync button in the ArgoCD web
UI, or argocd app sync.
Verify the deployment with ec ps:
ec ps
name label version ready deployed
t01-epics-pvcs service main True 2026-06-25T09:10:00Z
t01-epics-opis service main True 2026-06-25T09:10:00Z
t01-epics-gateways service main True 2026-06-25T09:10:00Z
bl01t-ea-fastcs-01 service main True 2026-06-25T09:14:00Z
And confirm the git record — pull the deployment repo and look at
apps/values.yaml:
git -C t01-deployment pull
services:
t01-epics-pvcs:
t01-epics-opis:
t01-epics-gateways:
bl01t-ea-fastcs-01:
enabled: true
targetRevision: main
labels:
description: ...
A new card for bl01t-ea-fastcs-01 also appears in the ArgoCD web UI.
Stop, start and remove a service (optional)#
The rest of the service lifecycle is also driven through ec:
ec stop bl01t-ea-fastcs-01/ec start bl01t-ea-fastcs-01— pause or resume a service as a live patch. Add--committo also record the change inapps/values.yaml(without--commit,selfHealwould otherwise revert a manual cluster change — the live patch sets the parameter directly).ec delete bl01t-ea-fastcs-01— removes the entry fromapps/values.yaml, commits and pushes; auto-sync then prunes the resources from the cluster.ec logs bl01t-ea-fastcs-01— stream a service’s logs.ec monitor— a terminal UI to browse and manage all services at once.
Clean up#
Because all desired state lives in git, teardown and rebuild are cheap and
reversible. Delete the root Application and, via prune plus finalizers, all of
its children are removed:
argocd app delete t01-beamline/t01 -y
Re-bootstrap any time with argocd app create --file t01-deployment/apps.yaml —
ArgoCD recreates everything from git (the persistent volume claims behind
t01-epics-pvcs are usually the slow part).
Note
The cluster is fully reconstructable from git, with one caveat: IOC autosave files held inside persistent volumes are not in git, so deleting the underlying PVCs discards that state.
Next steps#
How ArgoCD Deploys Your IOCs — the GitOps model in depth: two repositories, app-of-apps, auto-sync, and the
ecCLI’s role.The ec Backend Model — deploy with pure Helm instead of ArgoCD (the manual, non-GitOps alternative).
Add an IOC to the Kubernetes Beamline — add and configure your own IOC instances in a services repo, ready to deploy here.
Deploy the Example IOC Instance — the local /
docker composedeployment path, for contrast with this cluster-based CD flow.