Skip to content
Joey Wang
Menu

Search

DevOps and Reliability

Kaniko: building container images without a Docker daemon

Kaniko builds and pushes container images without a Docker daemon, and the setup for it differs across GitHub Actions, Google Cloud Build, and CircleCI.

· 2 min read

devops #kaniko#ci#kubernetes#docker

Audio summary

Kaniko builds a container image from a Dockerfile and pushes it to a registry without needing a Docker daemon anywhere in the process. That matters specifically in Kubernetes-based CI, where running a privileged Docker-in-Docker sidecar just to build an image is either disabled by policy or a genuine security liability. Kaniko runs entirely in userspace as a normal, unprivileged pod.

GitHub Actions

name: Build and Push Image

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Build and Push Image with Kaniko
        run: |
          mkdir -p /kaniko/.docker
          echo "{\"auths\":{\"<registry_url>\":{\"username\":\"$DOCKER_USERNAME\",\"password\":\"$DOCKER_PASSWORD\"}}}" > /kaniko/.docker/config.json
          /kaniko/executor \
            --dockerfile=Dockerfile \
            --context=. \
            --destination=<registry_url>/<image_name>:latest
        env:
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

Registry credentials come from GitHub secrets, written to /kaniko/.docker/config.json before the executor runs, since Kaniko reads Docker’s standard config format for auth rather than taking credentials as flags.

Google Cloud Build

Cloud Build runs Kaniko’s executor image directly as a build step, with the build context pulled from a storage bucket and credentials handled through Secret Manager:

steps:
  - name: 'gcr.io/kaniko-project/executor:latest'
    args:
      - "--dockerfile=Dockerfile"
      - "--context=gs://<your-bucket-name>/<path-to-context>/"
      - "--destination=gcr.io/<your-project-id>/<image-name>:latest"
    env:
      - "GOOGLE_APPLICATION_CREDENTIALS=/secret/gcp-key.json"

availableSecrets:
  secretManager:
    - versionName: "projects/<your-project-id>/secrets/<secret-name>/versions/latest"
      mountPath: "/secret/"

CircleCI

CircleCI runs the Kaniko executor image as the job’s own container, rather than invoking it as a separate build step, so credentials are assembled the same way as the GitHub Actions example, from environment variables set in the CircleCI project:

version: 2.1

jobs:
  build:
    docker:
      - image: gcr.io/kaniko-project/executor:latest
    steps:
      - checkout

      - run:
          name: Build and Push Image with Kaniko
          command: |
            mkdir -p /kaniko/.docker
            echo "{\"auths\":{\"$REGISTRY_URL\":{\"username\":\"$DOCKER_USERNAME\",\"password\":\"$DOCKER_PASSWORD\"}}}" > /kaniko/.docker/config.json
            /kaniko/executor \
              --dockerfile=Dockerfile \
              --context=. \
              --destination=$REGISTRY_URL/$IMAGE_NAME:latest

workflows:
  version: 2
  build-and-push:
    jobs:
      - build

The pattern is the same everywhere: write registry credentials to Kaniko’s expected config path, then point the executor at a Dockerfile, a context, and a destination. What changes between platforms is how the credentials and the build context get to the executor in the first place, not how Kaniko itself works.