Secure Docker Image Builds in Kubernetes: Moving from DinD to Kaniko

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

The Security Debt in Your CI/CD Pipeline

Kubernetes is the go-to for CI/CD, but building Docker images inside it creates a nagging security headache. Most teams hit a wall early on: how do you build a container image inside a container that is already running on a cluster node?

Many of us start with Docker-in-Docker (DinD) because it’s familiar. You’ve used Docker locally for years, so spinning up a daemon inside a pod feels like the path of least resistance. However, during a 2023 infrastructure audit at my previous firm, we found that this setup triggered multiple high-severity alerts. By granting privileged access to build containers, we were essentially handing out master keys to our entire production cluster.

Root Cause: Why Privileged Containers are a Liability

The problem lies in how Docker functions. It requires a background daemon (dockerd). To run this daemon inside a container, that container needs deep access to the host machine’s kernel to mount filesystems and manage network interfaces.

This usually leads to one of two risky configurations:

  1. Privileged Mode: Setting --privileged=true. This strips away nearly all security boundaries between the container and the host node. If a single build script is compromised, the attacker gains root access to the underlying physical or virtual machine.
  2. Docker Socket Binding: Mounting /var/run/docker.sock from the host. This is often worse. Any process with access to that socket can command the host’s Docker daemon to spin up new containers, wipe volumes, or pivot into other sensitive namespaces.

In a multi-tenant cluster, these setups are a disaster waiting to happen. They violate the principle of least privilege and create an attack surface that is nearly impossible to monitor effectively.

Comparing the Alternatives

When we decided to scrap DinD, we evaluated several tools designed to solve the “daemon problem.” Each has specific trade-offs.

Docker-in-Docker (DinD)

  • The Good: Full Docker feature set; zero learning curve for developers.
  • The Bad: Requires privileged access; slow due to nested filesystem overhead.

Docker Socket Binding

  • The Good: Extremely fast because it uses the host’s existing image cache.
  • The Bad: Massive security hole; one compromised container can take down the whole node.

Buildah

  • The Good: No daemon required; creates OCI-compliant images.
  • The Bad: Primarily built for rootless Podman; can be temperamental in standard Kubernetes environments.

Kaniko

  • The Good: No daemon needed; runs entirely in userspace; works natively with Kubernetes Pods without elevated host privileges.
  • The Bad: No local cache by default (it needs a remote registry to store layers).

The Solution: Why Kaniko Proved Most Resilient

Kaniko won our internal testing for Kubernetes-native workflows. Unlike Docker, it doesn’t need a daemon. Instead, Kaniko extracts the base image’s filesystem into the root of the container, executes Dockerfile commands, and takes a snapshot after every step. It then pushes these snapshots as layers directly to your registry.

After rolling this out to a 50-node cluster, our build reliability improved significantly. We no longer had to troubleshoot finicky sidecar daemons that failed to start. More importantly, our security team cleared the pipeline for production use within 24 hours.

Implementing Kaniko in Kubernetes

Setting up Kaniko requires two main components: your source code and the registry credentials.

1. Configuring Registry Credentials

Kaniko needs permission to push your finished image to Docker Hub, GCR, or ECR. We handle this via a Kubernetes Secret.

# Prepare your credentials
AUTH=$(echo -n "my_username:my_password" | base64)
cat <<EOF > config.json
{
  "auths": {
    "https://index.docker.io/v1/": {
      "auth": "$AUTH"
    }
  }
}
EOF

# Securely store the config in Kubernetes
kubectl create secret generic regcred \
    --from-file=.dockerconfigjson=config.json \
    --type=kubernetes.io/dockerconfigjson

2. The Kaniko Pod Template

This Pod definition runs the build. While this example uses a ConfigMap for the Dockerfile, in a production CI/CD setup, you would likely use a Git volume or a PersistentVolume.

apiVersion: v1
kind: Pod
metadata:
  name: kaniko-build
spec:
  containers:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:latest
    args:
    - "--dockerfile=Dockerfile"
    - "--context=dir:///workspace"
    - "--destination=your-repo/app-name:v1.2.0"
    volumeMounts:
    - name: kaniko-secret
      mountPath: /kaniko/.docker
    - name: source-code
      mountPath: /workspace
  restartPolicy: Never
  volumes:
  - name: kaniko-secret
    secret:
      secretName: regcred
      items:
        - key: .dockerconfigjson
          path: config.json
  - name: source-code
    configMap:
      name: app-source-code

3. Cutting Build Times with Caching

By default, Kaniko is slower than Docker because it lacks a local daemon cache. You can fix this by enabling the --cache=true flag. In our tests, using a dedicated cache repository reduced build times from 6 minutes down to 90 seconds for small code changes.

# Add these arguments to your container spec
args:
- "--dockerfile=Dockerfile"
- "--context=dir:///workspace"
- "--destination=your-repo/app-name:v1.2.0"
- "--cache=true"
- "--cache-repo=your-repo/kaniko-cache"

Lessons from the Field

Two things are worth noting when you switch. First, Kaniko runs as root inside its own container to manipulate the filesystem, but it doesn’t require host-level root privileges. This is the critical distinction that satisfies security auditors.

Second, if you use GitLab Runner or GitHub Actions with self-hosted runners, replacing docker build with a Kaniko container call is straightforward. Most modern tools support this natively. The transition typically takes an afternoon but eliminates the constant worry of container escapes and node compromises. By adopting a daemon-less architecture, you align your CI/CD with the security-first principles Kubernetes was built on.

Share: