Skip to content

Kaniko Docker Builds

CWIQ uses Kaniko to build Docker images inside Kubernetes pods without requiring privileged mode or Docker-in-Docker. This page explains the build process, image naming, tag strategy, and how the built image is passed between pipeline stages.


Why Kaniko

Docker-in-Docker (DinD) requires the runner pod to run in privileged mode, which is a security risk in a shared Kubernetes cluster. Kaniko builds Docker images entirely in userspace — it reads the Dockerfile and pushes the resulting image to a registry without needing a Docker daemon or elevated privileges.

The executor image is:

gcr.io/kaniko-project/executor:debug

This image is pulled through the Nexus Docker proxy cache, not directly from Google Container Registry:

nexus.shared.cwiq.io:8444/gcr.io/kaniko-project/executor:debug

Kaniko uses BusyBox — not a full shell

The kaniko/executor:debug image provides a minimal BusyBox shell for the entrypoint. Standard shell utilities may not behave identically to bash. In particular, use /kaniko/.docker/config.json to provide registry credentials — do not call docker login, which is not available.


Registry Authentication

Kaniko authenticates to Nexus using a config.json file written to /kaniko/.docker/config.json before the build starts. The credentials are fetched from Vault using GitLab's JWT authentication in the job's before_script:

before_script:
  - |
    # Authenticate with Vault via GitLab JWT
    VAULT_TOKEN=$(curl -s --request POST \
      "${VAULT_ADDR}/v1/auth/jwt/login" \
      --data "{\"jwt\": \"${CI_JOB_JWT_V2}\", \"role\": \"gitlab-ci\"}" \
      | python3 -c "import sys,json; print(json.load(sys.stdin)['auth']['client_token'])")

    # Fetch Nexus push credentials
    NEXUS_USER=$(curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
      "${VAULT_ADDR}/v1/secret/nexus/svc-orchestrator" \
      | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['username'])")
    NEXUS_PASS=$(curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
      "${VAULT_ADDR}/v1/secret/nexus/svc-orchestrator" \
      | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['password'])")

    # Write Kaniko auth file
    mkdir -p /kaniko/.docker
    echo "{\"auths\":{\"nexus.shared.cwiq.io:8443\":{\"username\":\"${NEXUS_USER}\",\"password\":\"${NEXUS_PASS}\"}}}" \
      > /kaniko/.docker/config.json

Image Naming Convention

All CWIQ Docker images follow the pattern orchestrator-{project} — note there is no cwiq- prefix on application-level image names.

Project Image Name Full Push Path
server orchestrator-server nexus.shared.cwiq.io:8443/orchestrator-server
ui orchestrator-ui nexus.shared.cwiq.io:8443/orchestrator-ui
agent orchestrator-agent nexus.shared.cwiq.io:8443/orchestrator-agent
mcp orchestrator-mcp nexus.shared.cwiq.io:8443/orchestrator-mcp
runner-api orchestrator-runner-api nexus.shared.cwiq.io:8443/orchestrator-runner-api
iam-api orchestrator-iam-api nexus.shared.cwiq.io:8443/orchestrator-iam-api
notification-api orchestrator-notification-api nexus.shared.cwiq.io:8443/orchestrator-notification-api

Push port: 8443 (Nexus hosted Docker repository, write access)

Pull port: 8444 (Nexus Docker group repository, includes proxy cache for Docker Hub and GCR)


Tag Strategy

Trigger Tags Applied Example
Push to main main-{short-sha}, latest main-a1b2c3d, latest
Feature branch branch-{slug}-{short-sha} branch-feature-search-a1b2c3d
Version tag (v*) {version}, stable 1.0.0, stable

The short-sha is the first 8 characters of $CI_COMMIT_SHA. The branch slug replaces non-alphanumeric characters with hyphens and truncates to 40 characters.

latest is pushed by the push-latest job in the push stage (separate from the build stage) and runs only on main.


The build.env Artifact

The build job writes a single-line artifact file that records the exact image tag produced:

IMAGE_TAG=main-a1b2c3d

This file is declared as a dotenv artifact in the job definition:

artifacts:
  reports:
    dotenv: build.env

GitLab's dotenv artifact type automatically injects the variables into any downstream job that uses needs: with artifacts: true. This means the deploy-dev, trivy-image-scan, and push-latest jobs all automatically have $IMAGE_TAG available as an environment variable without any manual parsing.

Always reference $IMAGE_TAG from build.env, never construct the tag manually

Constructing the image tag manually in deploy or scan jobs (e.g., main-${CI_COMMIT_SHORT_SHA}) creates a risk of mismatch if the build job uses different logic. Always use $IMAGE_TAG injected from build.env.


Build Arguments

The Kaniko executor passes these build arguments to every Dockerfile via --build-arg:

Build Arg Value Example
VERSION $CI_COMMIT_TAG (empty on non-tag pipelines) 1.2.0
BUILD_DATE ISO 8601 timestamp 2026-03-18T09:00:00Z
VCS_REF Full commit SHA ($CI_COMMIT_SHA) a1b2c3d4e5f6...

These are typically stored as OCI image labels in the Dockerfile:

ARG VERSION
ARG BUILD_DATE
ARG VCS_REF

LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"
LABEL org.opencontainers.image.revision="${VCS_REF}"

Build Cache

Kaniko uses a layer cache stored in Nexus to speed up repeated builds. The cache repository is separate from the image repository:

--cache=true
--cache-repo=nexus.shared.cwiq.io:8443/orchestrator-{project}/cache

Cache hits are most effective when RUN pip install or RUN npm install layers are placed before COPY . . in the Dockerfile, so that dependency installation is only re-executed when requirements.txt or package.json changes.


Full Kaniko Executor Command

The build-push job runs Kaniko with the following arguments (simplified for readability):

/kaniko/executor \
  --context "${CI_PROJECT_DIR}" \
  --dockerfile "${CI_PROJECT_DIR}/Dockerfile" \
  --destination "nexus.shared.cwiq.io:8443/orchestrator-${CI_PROJECT_NAME}:main-${CI_COMMIT_SHORT_SHA}" \
  --destination "nexus.shared.cwiq.io:8443/orchestrator-${CI_PROJECT_NAME}:latest" \
  --cache=true \
  --cache-repo="nexus.shared.cwiq.io:8443/orchestrator-${CI_PROJECT_NAME}/cache" \
  --build-arg "VERSION=${CI_COMMIT_TAG}" \
  --build-arg "BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --build-arg "VCS_REF=${CI_COMMIT_SHA}"