building-docker-images
| Type | Skill |
| Plugin | awl-cicd · v0.0.22 |
| Invoke | /awl-cicd:building-docker-images |
| Tools | Read, Write, Edit, Bash, Glob, Grep |
| Source | plugins/awl-cicd/skills/building-docker-images/SKILL.md |
When Claude uses it
Section titled “When Claude uses it”This skill should be used when the user asks to “add a docker build”, “write a Dockerfile”, “build the image in CI”, “multi-arch image”, “buildkit”, “docker build fails in CI”, “private npm packages in docker”, or hits registry 401/404 errors. Builds Docker images in GitLab CI/CD with the AWL components (build-buildkit, build-docker-simple, build-docker-multiarch), passes build arguments and private registry tokens, and diagnoses builds that pass tests but fail in the image.
Trigger phrases: add a docker build · write a Dockerfile · build the image in CI · multi-arch image · buildkit · docker build fails in CI · private npm packages in docker
Definition
Section titled “Definition”Which template
Section titled “Which template”| Situation | Template |
|---|---|
App deploys with deploy-default-branches |
none, it includes build-docker-simple as build-docker |
| Standalone build, new project | build-buildkit (in-cluster BuildKit, no dind, registry cache) |
Standalone build on the docker runner |
build-docker-simple |
linux/amd64 + linux/arm64 |
build-docker-multiarch |
Legacy develop/release flow with version |
build-docker |
Quick start (BuildKit)
Section titled “Quick start (BuildKit)”include: - component: $CI_SERVER_FQDN/devops/ci-cd-templates/build-buildkit@main inputs: build_dir: . dockerfile: Dockerfile tag: $CI_COMMIT_REF_SLUG cache_tag: cache
build-image: extends: .build-buildkit rules: - if: '$CI_COMMIT_BRANCH == "main"'
build-image-mr: extends: .build-buildkit variables: PUSH: "false" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event"Build args go through extra_args: "--opt build-arg:API_URL=https://..." (BuildKit syntax), not --build-arg.
Private npm registry (most common issue)
Section titled “Private npm registry (most common issue)”For private @awl/* packages pass CI_JOB_TOKEN into the build.
.gitlab-ci.yml (build-docker-simple / deploy-default-branches):
build-docker: variables: BUILD_ARG: "--build-arg CI_JOB_TOKEN=${CI_JOB_TOKEN}"Dockerfile:
FROM registry.appswithlove.net/devops/docker-images/node:22-alpine AS depsWORKDIR /appARG CI_JOB_TOKENCOPY package.json pnpm-lock.yaml .npmrc* ./RUN CI_JOB_TOKEN=${CI_JOB_TOKEN} pnpm i --frozen-lockfile.npmrc:
@awl:registry=https://gitlab.appswithlove.net/api/v4/packages/npm///gitlab.appswithlove.net/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN}The AWL node images already contain pnpm and the registry config; plain node:*-alpine needs corepack enable pnpm.
Build-time secrets (Sentry sourcemaps and similar)
Section titled “Build-time secrets (Sentry sourcemaps and similar)”A build arg that comes from an environment-scoped CI variable is empty unless the build job has an environment: block:
build-docker: environment: name: staging action: verify variables: BUILD_ARG: >- --build-arg SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN} --build-arg SENTRY_RELEASE=${CI_COMMIT_SHORT_SHA}Without it the upload step in the image runs with an empty token and usually fails silently.
Template reference
Section titled “Template reference”build-buildkit
Section titled “build-buildkit”Hidden job .build-buildkit, image moby/buildkit:*-rootless, talks to the cluster BuildKit daemon.
| Input / variable | Default | Description |
|---|---|---|
build_dir |
. |
Build context |
dockerfile |
Dockerfile |
Path relative to the workspace |
image |
$CI_REGISTRY_IMAGE |
|
tag |
$CI_COMMIT_REF_SLUG |
|
extra_args |
"" |
Extra buildctl build args, e.g. --opt build-arg:K=V |
push |
true |
PUSH: "false" on a job builds without pushing |
platform |
"" |
e.g. linux/amd64,linux/arm64; empty = native |
cache_tag |
"" |
Registry cache image tag; empty disables caching |
buildkit_host |
tcp://buildkit:1234 |
|
stage |
build |
Inputs resolve at include time. Per-job overrides use the upper-case variables (PUSH, IMAGE_TAG, DOCKERFILE, BUILD_DIR, EXTRA_ARGS, PLATFORM, CACHE_TAG).
build-docker-simple
Section titled “build-docker-simple”Hidden job .docker-build-simple, runs docker build on the docker runner.
| Variable | Default | Description |
|---|---|---|
BUILD_DIR |
. |
Dockerfile directory |
BUILD_ARG |
"" |
--build-arg K=V ... |
IMAGE_NAME |
$CI_REGISTRY_IMAGE |
|
IMAGE_TAG |
$CI_COMMIT_REF_NAME |
build-docker-multiarch
Section titled “build-docker-multiarch”Hidden job .docker-build-multiarch, Docker Buildx with registry build cache and reproducible-build flags.
| Input | Default | Description |
|---|---|---|
platforms |
linux/amd64,linux/arm64 |
|
build_dir |
. |
|
dockerfile |
Dockerfile |
|
build_arg |
"" |
--build-arg K=V |
image_tag |
$CI_COMMIT_REF_SLUG |
|
cache_tag |
cache |
|
image |
docker:cli |
Job image |
Matrix builds override the upper-case variables per cell (IMAGE_TAG, DOCKERFILE, CACHE_TAG), not the inputs. DOCKER_PUSH: "false" for MR validation.
build-docker (legacy)
Section titled “build-docker (legacy)”Tags IMAGE:develop on develop and IMAGE:release-<VERSION> on main/release. Needs the version template. Inputs build_dir, build_arg.
build-npm
Section titled “build-npm”Non-Docker artifact build: NODE_IMAGE, NPM_INSTALL, NPM_BUILD, ARTIFACTS_FOLDER, WORKING_DIR.
Troubleshooting
Section titled “Troubleshooting”| Error | Cause | Fix |
|---|---|---|
ERR_PNPM_FETCH_404 / “No authorization header” |
Missing CI_JOB_TOKEN |
Add BUILD_ARG: "--build-arg CI_JOB_TOKEN=${CI_JOB_TOKEN}" |
| Permission denied on registry | Missing .npmrc |
COPY .npmrc* ./ before install |
| Build arg not used | Wrong variable name | BUILD_ARG (upper case), not build_arg |
| Docker login fails | Missing registry vars | Check CI_REGISTRY_* are available |
Tests green, pnpm build fails only in Docker |
.dockerignore excludes a file the build imports (e.g. test/ used by vitest.config.ts) |
Exclude the importing config too, or stop importing from ignored paths. Reproduce with the exact Docker step locally |
| Build arg from a scoped variable is empty | Job has no environment: |
Add environment: { name: <env>, action: verify } |
| Image builds but the container has no icon/asset/config | File added after the image was built, or PoC never rebuilt | Rebuild; PoC deployments need Renovate or a kill date |
| Sourcemap upload silently skipped | Empty SENTRY_AUTH_TOKEN in the build |
See build-time secrets |
Best practices
Section titled “Best practices”- Copy
.npmrc*beforenpm install/pnpm install. - Use multi-stage builds; the runtime stage ships only build output.
- Pin base images (
node:22-alpine, notnode:latest); prefer the AWL images for@awl/*packages. - Use
--frozen-lockfile. - Keep
.dockerignoreand the build’s imports consistent. CI tests run on a full checkout and don’t catch what the image can’t see. - Never bake secrets into layers. Build-time tokens go through build args and stay in the build stage.
- Rebuild deployed images regularly. An image from months ago carries the vulnerabilities of that month; a showcase on
natron-stagingwas exploited through an unpatched Next.js in 2026.
Example: complete SvelteKit build
Section titled “Example: complete SvelteKit build”FROM registry.appswithlove.net/devops/docker-images/node:22-alpine AS depsWORKDIR /appARG CI_JOB_TOKENCOPY package.json pnpm-lock.yaml .npmrc* ./RUN CI_JOB_TOKEN=${CI_JOB_TOKEN} pnpm i --frozen-lockfile
FROM registry.appswithlove.net/devops/docker-images/node:22-alpine AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .RUN pnpm build
FROM registry.appswithlove.net/devops/docker-images/node:22-alpineWORKDIR /appCOPY --from=builder /app/build ./buildCOPY --from=builder /app/package.json ./EXPOSE 3000CMD ["node", "build"]The steps/build-sveltekit-docker CI step ships this Dockerfile as a shared, versioned file (see gitlab-cicd/references/templates/utilities.md).
Reference
Section titled “Reference”- Full template sources: references/TEMPLATES.md
- Dockerfile and pipeline examples: references/EXAMPLES.md

