Step 4 — .gitlab-ci.yml and the first image
The pipeline is a handful of include: lines pulling shared components from the
oym-k8s-deploy-templates catalog. You don't write
jobs; you list components.
The file
Node / SvelteKit project:
---
# OYM k8s deploy components: https://gitlab.com/onyourmarks/k8s/oym-k8s-deploy-templates
include:
- component: "$OYM_COMPONENT_URL/base-variables@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-test-eslint@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-test-prettier@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-test-stylelint@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-test-vitest@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-build-image@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-tag-environment@1.1.0"
variables:
DEPLOY_ENVIRONMENTS: "staging,production"
PHP project:
---
# OYM k8s deploy components: https://gitlab.com/onyourmarks/k8s/oym-k8s-deploy-templates
include:
- component: "$OYM_COMPONENT_URL/base-variables@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-test-phpstan@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-test-phpunit@1.1.0"
# add eslint / prettier / stylelint if the project has a frontend build
- component: "$OYM_COMPONENT_URL/stage-build-image@1.1.0"
- component: "$OYM_COMPONENT_URL/stage-tag-environment@1.1.0"
variables:
DEPLOY_ENVIRONMENTS: "staging,production"
Drop any stage-test-* line whose tool the project doesn't have (no pnpm test script → no vitest, no
phpstan.neon → no phpstan). eslint, prettier and stylelint are non-blocking; vitest, phpunit and
phpstan fail the pipeline.
If the project only has one environment, set DEPLOY_ENVIRONMENTS: "production" and, in step 5, list only that
environment.
Optional overrides in variables:: NODE_VERSION (default 24), PNPM_VERSION (11), PHP_VERSION (8.5).
What the pipeline does
| Job | Stage | Runs | Result |
|---|---|---|---|
test:* | test | automatically | Lint / test feedback. |
build:image | build | automatically, every push | registry.gitlab.com/onyourmarks/k8s/<slug>/app:<sha> from .deploy/docker/build/app.Dockerfile. |
tag:image:staging, tag:image:production | tag | manually, you click it | Re-tags that image as <sha>-staging / <sha>-production, notifies Kargo, then waits until ArgoCD reports the deploy healthy and records a GitLab Deployment. |
There is no branch-to-environment rule. Any branch, any commit: once build:image is green you can click
tag:image:staging. Clicking tag:image:production on the same pipeline promotes the same image, no rebuild.
Before step 6 the tag job will still succeed in re-tagging but then time out waiting for ArgoCD, because the app
doesn't exist on the cluster yet. That's expected.
The tag jobs live in a child pipeline (trigger:tag:pipeline). In the GitLab UI open the pipeline, click the
downstream pipeline, and the manual buttons are there.
Variables you do NOT set
These are inherited from the onyourmarks/k8s group and must not be redefined in the project:
| Variable | Purpose |
|---|---|
OYM_COMPONENT_URL | Points include: at the catalog. |
ARGOCD_KARGO_WEBHOOK_URL | The tag job posts {"warehouse": "<slug>-<env>"} here so Kargo refreshes instantly. |
ARGOCD_TOKEN | Lets the tag job poll ArgoCD for the deploy result. |
That is also why the project must live in that group: elsewhere the variables don't exist and the pipeline fails
on the first include.
Traps
CI_PROJECT_NAMEmust equal the slug. The tag job derives the Kargo warehouse name from it. Renaming the GitLab project silently breaks deploys.- Don't copy
.gitlab-ci.ymlfromoym-paddock-feorteam-yellowb-tourshirt-2026. They use vendoredlocal:templates from before Kargo existed. DEPLOY_ENVIRONMENTSmust matchenvironmentsin step 5. A tag job for an environment that isn't defined on the cluster produces an image nobody picks up.- Legacy CI leftovers. If the old file had
SSH_STAGING,stage-deploy,deploy.phpor a.gitlab/folder of scripts, delete all of it. The new file above is complete on its own.
First pipeline
- Commit
.deploy/and.gitlab-ci.yml, push a branch. - Wait for
build:imageto go green. If it fails, the log shows the Docker build error; fix locally with the test from step 3 and push again. - Open the downstream pipeline and click
tag:image:staging. It will re-tag the image and then poll ArgoCD; that poll fails now (no app yet). Fine. - Verify the tag exists: GitLab project → Deploy → Container Registry →
app→ you see<sha>and<sha>-staging. Kargo picks that tag up once step 6 is done.
Let Claude Code do it
Phase 4 of the project prompt from the prompt generator writes the file and, if you ticked "Replaces a legacy VM deploy", removes the old deploy flow.
Done when
-
.gitlab-ci.ymlmatches the template above, pinned at@1.1.0 -
build:imageis green on your branch -
tag:image:stagingwas clicked and the registry shows<sha>-staging