Create Docker Images without Docker daemon (Kaniko)

Sarva Bhowma
Geek Culture
Published in
6 min readAug 3, 2021

The well-known security flaw in Docker is that it requires root access to build your Docker images with the Docker daemon.

We know that we should be careful when we are using root access. This article will help to understand the downsides of using docker and one of the Docker alternative (Kaniko) to mitigate the security issues.

Docker Inside Docker

Known Problem with Docker inside Docker:

To understand why docker daemon is running with root access and how it’s a problem, we first need to understand the Docker high level architecture.

Container images are specified with the Dockerfile. The Dockerfile details how to build an image based on your application and resources. Using Docker, we can use the build command to build our container image. Once you have the image of your Dockerfile, you can run it. Upon running the image, a container is created.

Below is a simplified diagram of the Docker architecture, taken directly from the official documentation.

Docker Architecture

The problem with docker is we can’t use the docker directly on your system. In most cases, we will only interact with the Docker CLI. However, running an application with Docker means that you have to run the Docker Daemon with root privileges. It actually binds to a Unix socket instead of a TCP port. By default, users can only access the Unix socket using sudo command, which is owned by the user root.

The Docker Daemon is responsible for the state of your containers and images, and facilitates any interaction with “the outside world.” The Docker CLI is merely used to translate commands into API calls that are sent to the Docker Daemon. This allows you to use a local or remote Docker Daemon.

Google solves this problem by providing a tool called Kaniko. Kaniko helps you build container images within a container without any access to the Docker daemon. That way, you can execute your build jobs within containers without granting any access to the host filesystem.

Kaniko:

Kaniko is an open source tool that allows users to build images even without granting it root access. These images are built inside a container or a Kubernetes cluster. Unlike the Docker daemon, Kaniko executes all commands inside the userspace. Kaniko is maintained by Google.

There are several different ways to deploy and run Kaniko:

To run a container, Kaniko needs three arguments:

  1. A Dockerfile
  2. A Build Context: The directory containing a Dockerfile which Kaniko can use to build your image.
  3. The name of the registry to which the final image should be pushed.

How Kaniko works

  • Reads the specified Dockerfile.
  • Extracts the base image (specified in the FROM directive) into the container filesystem.
  • Runs each command in the Dockerfile individually.
  • Takes a snapshot of the userspace filesystem after every run.
  • Appends the snapshot layer to the base layer on each run.

Because of this, Kaniko does not depend on a Docker daemon.

Start with Kaniko:

We will use Kaniko inside a Kubernetes Cluster. To get started with Kaniko and to follow the next steps, we assume that you have the following set-up:

  • A running Kubernetes cluster with permissions to create, list, update and delete jobs, services, pods, and secrets.
  • A GitHub account for storing the Dockerfile and Kubernetes manifests.
  • A Docker Hub account for hosting container images.

Create Secret for Container Registry

It’s necessary to authenticate with the container registry to push the built image. So ensure that its created in the cluster.

You will need the following:

  • docker-server — The Docker registry server where you need to host your images. If you are using Docker Hub use https://index.docker.io/v1/.
  • docker-username — The Docker registry username.
  • docker-password — The Docker registry password.
  • docker-email — The email configured on the Docker registry.

Run the following command, substituting the necessary values:

kubectl create secret docker-registry regcred --docker-server=<docker-server> --docker-username=<username> --docker-password=<password> --docker-email=<email>

For a testing I am using nignx image and I have already dockerfile and kaniko yaml job & test loads to test the image.

First lets take a look at Dockerfile.

The Dockerfile contains two steps. It declares the base image to nginx and writes This image is created by kanikoto /usr/share/nginx/html/index.html. We should get that as a response when we hit the NGINX endpoint.

Let’s see what the kaniko.yaml looks like:

The manifest creates a container using the gcr.io/kaniko-project/executor:latest image and runs it with the following arguments:

  • docker-file — The path of the Docker file, relative to the context.
  • context — The Docker context. In this case, we’ve indicated our GitHub repository
  • destination — The Docker repository to push the built image.

Additionally, it also mounts a docker config JSON file on /kaniko/.docker to authenticate with the Docker repository. We defined this in the previous section.

Build the Container Image Using Kaniko

Build the image by applying the kaniko.yaml manifest:

[node1 kaniko]$ kubectl apply -f kaniko.yaml
job.batch/kaniko created
[node1 kaniko]$ k get po
NAME READY STATUS RESTARTS AGE
kaniko-rcdqj 1/1 Running 0 5s
[node1 kaniko]$
[node1 kaniko]$ k get po
NAME READY STATUS RESTARTS AGE
kaniko-rcdqj 0/1 Completed 0 16s
[node1 kaniko]$

Below is the logs snippet from the kaniko pod.

Kaniko Logs to verify the image build & Push

Currently new image is being pushed to dockerhub.

Docker Hub Image

Testing the created Image:

To test this we have created simple deployment file to use the kaniko created image and print the page.

nginx-deployment.yaml file:

Here under spec, containers, image section i have used the custom image name which we have created through the kaniko.

nginx-service.yaml file:

In the above service we are exposing the app to the internet to test.

Now apply these two manifest files to create the application with the image which we have created through kaniko.

[node1 tests]$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
[node1 tests]$ kubectl apply -f nginx-service.yaml
service/nginx-service created
[node1 tests]$
[node1 tests]$
[node1 tests]$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/kaniko-rcdqj 0/1 Completed 0 10m
pod/nginx-deployment-7dd46f65d-d96v7 1/1 Running 0 23s
pod/nginx-deployment-7dd46f65d-dqz7f 1/1 Running 0 23s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11m
service/nginx-service NodePort 10.103.233.221 <none> 80:31065/TCP 15s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-deployment 2/2 2 2 23s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-deployment-7dd46f65d 2 2 2 23s
NAME COMPLETIONS DURATION AGE
job.batch/kaniko 1/1 14s 10m
[node1 tests]$

As we can see test app is up and running fine with the image we have created, let do the small curl/web test.

Since I have enabled nodePort I have to hit kubernetes node IP with the nodePort port number to test the application.

[node1 tests]$ curl http://ip172-19-0-11-c44f1k975d7000bk418g.direct.labs.play-with-k8s.com:31065/
This image is created by kaniko
[node1 tests]$

Above is the curl test.

That’s it for the post. I hope you would have got some clarity on Kaniko and how it works. I will come up with more advanced topics in coming weeks. Till then, Take care and Stay Safe.

Keep Learning!

Fork this repository into your GitHub account for all exercise files.

--

--