Deploy an update
Most deploys are one git commit. ArgoCD sees the change, applies it, the pods roll. No buttons pressed, no SSH, no kubectl apply.
The three shapes of "an update"
Nearly every change falls into one of these:
1. Bumping an image version (most common)
Example: a dev team shipped 4.99.11.0-wecare and you want to deploy it.
# Edit the image tag in the relevant manifest
$EDITOR manifests_v1/app-constructs/ecommercen-clients/wecare/adveshop4/base/kustomization.yamlFind the images: section:
images:
- name: ecommercen/adveshop4-php_fpm
newTag: 4.99.10.0-wecare # ← change this
- name: ecommercen/adveshop4-php_cli
newTag: 4.99.10.0-wecareBump to the new tag, save, then:
git add -p # review diff line by line
git commit -m "[App: wecare] Bump image to 4.99.11.0-wecare"
git pushWithin ~1 minute ArgoCD picks up the push. The Deployment's spec.template.spec.containers[*].image changes. Kubernetes' Deployment controller rolls pods one-by-one (maxSurge: 50%, maxUnavailable: 0 → always at least the old count running until new ones are Ready).
Getting the -client suffix right
The version number is the easy half. The suffix — -wecare, -seajets, -v4-evripidis — is not derived from the release tag. It is the ECOMMERCEN_CLIENT variable on the client's source repo, and it overrides whatever the git tag says. A release git-tagged 4.118.0.1.seajets-admin ships as …app_bootstrap:4.118.0.1-seajets.
Two forms of the same build, and only one of them is a valid newTag::
| Form | Example | |
|---|---|---|
Git tag / version.php | all dots | 4.118.0.1.seajets-admin |
Docker image tag (what newTag: takes) | hyphen before the client | 4.118.0.1-seajets |
Practical rule: keep the suffix already in the file and change only the numbers. Don't copy a suffix from a release tag, a changelog, or the directory name you're editing — all three can differ from what the pipeline actually pushed.
Current suffixes in use:
| Client | Suffix |
|---|---|
| wecare | -wecare |
| seajets | -seajets (moving to -seajets-admin) |
| evripidis | -evripidis |
| evaspharmacy | -evaspharmacy |
| pharmafragakis | -pharmafragakis |
| demo | none — pins the upstream form, e.g. 4.117.0 |
The suffix can change between releases, because it's a pipeline variable rather than a property of the client. If a bump doesn't come up, the tag probably doesn't exist: the images are in a private Docker Hub repo, so confirm against the registry (with the regcred creds) instead of guessing.
A wrong suffix silently kills cron
Worth knowing before it happens to you. app-cron is a CronJob with concurrencyPolicy: Forbid. A bad pin creates a Job that sits in ImagePullBackOff forever — and because that Job never finishes, every later run is skipped with JobAlreadyActive. Cron just stops, with no failing Deployment to draw your attention.
Correcting the pin fixes the web pods but does not clear the stuck Job. Delete it by hand:
kubectl -n ecommercen-clients-realm-1-evripidis get jobs
# the stuck one shows Running with 0/1 completions and an age far past its duration
kubectl -n ecommercen-clients-realm-1-evripidis delete job app-cron-<id>The next scheduled run then starts clean on the corrected pin. (This happened on 2026-07-28: an evripidis pin went in for a suffix the pipeline hadn't pushed under that name, and cron stayed dead for 68 minutes — long after the web pods had recovered.)
2. Changing a config setting
Example: wecare needs pm.max_children raised from 50 to 75.
$EDITOR manifests_v1/app-constructs/ecommercen-clients/wecare/adveshop4/base/app.yaml
# find the `app-web-fpm-www-cm` ConfigMap, edit the valueCommit + push. ArgoCD applies the new ConfigMap. Stakater Reloader notices the ConfigMap changed and triggers a rolling restart of the Deployment that mounts it. No manual pod action needed.
3. Upgrading an upstream component (ArgoCD, Longhorn, etc)
We have a scripted workflow for this:
python3 scripts/check-updates.py # see what's outdated
python3 scripts/do-updates.py --dry-run # preview
python3 scripts/do-updates.py # apply all safe updates + commit
git push origin mainThe scripts read scripts/apps.yaml (single source of truth for versions). They know which updates are safe-auto (patch bumps), which need a human (major bumps, skip_major: true), and which are manual-only (e.g., operators that require migration steps).
See Tracked apps for the list.
How to tell it landed
Three signals, in increasing confidence order:
1. ArgoCD status
argocd app get app-wecare-adveshop4-prod
# or click the app in the web UILook for:
Sync Status: SyncedHealth Status: Healthy(Progressing while rolling out, which is fine)
2. Pod age / image
kubectl -n ecommercen-clients-wecare \
get pods -l app.kubernetes.io/name=app-web -o wideNew pods should have a recent AGE and the new image. If any pod is still on the old image after a few minutes, the rollout got stuck — see Check app status.
3. Live behaviour
Hit the site, watch Grafana. For wecare, open the Operations dashboard — the P95 latency shouldn't spike, 5xx rate should stay near 0 through the rollout.
Rolling back
The standard answer is not kubectl rollout undo. That works but gets reverted by ArgoCD within a minute because it doesn't match the repo.
The right answer: git revert the commit, push. Same flow as a deploy, just backwards.
git log --oneline | head -5 # find the commit to undo
git revert <commit-sha>
git pushArgoCD syncs the revert; pods roll back to the previous image. Full audit trail in git.
When things get ambitious
| Change | Shape | Risk |
|---|---|---|
| Image tag bump (patch version) | Simple commit | Low — rolling update, easy revert |
| Image tag bump (major version) | Simple commit but test staging first | Medium — may require DB migration |
| New env var | Commit + Reloader restart | Low |
| KEDA scaling threshold | Commit, next scale event picks it up | Low |
| New Ingress hostname | Commit + Cloudflare DNS record | Medium — DNS propagation |
| New client onboarding | See Add a client | Medium — many moving parts |
| MariaDB major version | Operator recovery + db-manager agent | High — test on staging first |
| Kubernetes version bump | Terraform-driven node replacement + staged rollout | High — maintenance window |
When in doubt
Delegate to the relevant Claude agent — they know the patterns and the safety rails:
- Image bumps / routine: just go.
- App updates via scripts/apps.yaml: the update-checker agent (or
do-updates.py+ read the output). - Client-scoped changes: the gitops-commit-pusher + argocd-manager agents for a sanity-checked flow.
- Anything touching DB / Redis state: the db-manager agent first, always.
Further reading
- GitOps & ArgoCD
- Check app status if the deploy looks stuck
- Incident response if the deploy broke something