How-To: GitLab CI/CD → Build Docker, Push to Registry, Helm Deploy to RKE2


Updated: 2025-08-24

Overview

Set up a GitLab pipeline that builds a container image, pushes it to a private GitLab Container Registry, and deploys to RKE2 via Helm. This edition includes an expanded Kaniko section with practical executor flags and templates.

Dockerfile (multi-stage, Go example; swap for your stack):

# syntax=docker/dockerfile:1
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app

FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=build /src/app ./app
USER 65532:65532
ENTRYPOINT ["./app"]

Create imagePullSecret (run once per namespace):

kubectl create secret docker-registry regcred   --docker-server=gitlab.local:5050   --docker-username=<deploy_user_or_token_name>   --docker-password=<deploy_token_or_pat>   --docker-email=none@example.com   -n <namespace>

.gitlab-ci.yml (Docker-in-Docker build):

stages: [build, deploy]
variables:
  IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"

build:
  stage: build
  image: docker:26
  services: [docker:26-dind]
  script:
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
    - docker build -t "$IMAGE" .
    - docker push "$IMAGE"
  only: [branches]

deploy:
  stage: deploy
  image: alpine/helm:3.14.4
  before_script:
    - apk add --no-cache curl bash
    - mkdir -p ~/.kube && echo "$KUBECONFIG_CONTENTS" > ~/.kube/config
  script:
    - helm upgrade --install myapp ./helm/myapp -n <namespace> --create-namespace         --set image.repository="$CI_REGISTRY_IMAGE"         --set image.tag="$CI_COMMIT_SHORT_SHA"         --set imagePullSecrets[0].name="regcred"
  only: [branches]

Kaniko build (no DinD) — Expanded Options

This job shows common flags you’ll actually use: multiple destinations (SHA + latest), cache repo, build args, labels, TLS toggles, and artifacts like digest/tar.

build_kaniko:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.23.2-debug    # pick a pinned version
    entrypoint: [""]                                       # allow running /kaniko/executor directly
  variables:
    IMAGE_REPO: "$CI_REGISTRY_IMAGE"                       # e.g. gitlab.local:5050/group/project
    IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"
    IMAGE_LATEST_TAG: "latest"
    DOCKERFILE: "Dockerfile"
    CONTEXT: "$CI_PROJECT_DIR"
    CACHE_REPO: "$CI_REGISTRY_IMAGE/cache"                 # registry path to store the cache layers
    # Optional extras:
    KANIKO_EXTRA_ARGS: "--snapshotMode=redo --use-new-run --verbosity=info"
  script:
    # Auth: use CI-provided credentials
    - mkdir -p /kaniko/.docker
    - >
      echo "{"auths":{"$CI_REGISTRY":{"username":"$CI_REGISTRY_USER",
      "password":"$CI_REGISTRY_PASSWORD"}}}" > /kaniko/.docker/config.json

    # Build and push to SHA tag and 'latest'
    - >
      /kaniko/executor
      --context $CONTEXT
      --dockerfile $DOCKERFILE
      --destination $IMAGE_REPO:$IMAGE_TAG
      --destination $IMAGE_REPO:$IMAGE_LATEST_TAG
      --build-arg GIT_SHA=$CI_COMMIT_SHA
      --build-arg VERSION=${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}
      --label org.opencontainers.image.revision=$CI_COMMIT_SHA
      --label org.opencontainers.image.source=$CI_PROJECT_URL
      --cache=true
      --cache-repo $CACHE_REPO
      --cache-ttl=168h                                 # 7d cache
      --digest-file $CI_PROJECT_DIR/image-digest.txt
      $KANIKO_EXTRA_ARGS
  artifacts:
    when: always
    paths: [image-digest.txt]
  only: [branches]

Using CI_JOB_TOKEN instead of CI_REGISTRY_PASSWORD

# GitLab’s built-in token (works on same GitLab instance)
echo "{"auths":{"$CI_REGISTRY":{"username":"gitlab-ci-token","password":"$CI_JOB_TOKEN"}}}" > /kaniko/.docker/config.json

Pinning your image name explicitly

# If you want a different repo path than $CI_REGISTRY_IMAGE:
variables:
  IMAGE_REPO: "gitlab.local:5050/local/your-service"
  IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"
# Then use --destination $IMAGE_REPO:$IMAGE_TAG

Security Notes

- Mask and protect registry credentials; use deploy/CI tokens with least privilege.
- Sign images after push (Cosign) and verify at admission.
- Avoid pushing ':latest' from feature branches unless you truly want it.
- Keep your Dockerfile free of secrets; use build args only for non-sensitive data.

Meme idea

“It works on my machine” — Kaniko: *cool story, push the layers.*

Taylor Swift

“You can aim for my heart, go for blood / But you would still miss me in your bones.” — I Did Something Bad

Comments

Popular posts from this blog

Learning to Automate My Side Projects with SWE-agent + GitLab

Ship-Ready Web Essentials: Search, Sitemap, Metadata & Icons (SvelteKit)

Kubernetes Secrets Management — SOPS + age (GitOps‑friendly)