Multi-Stage Docker Builds for Kubernetes

Tin Rabzelj
Tin RabzeljHire me

31 October, 2017

Building and compiling apps inside a Docker image can produce very large images. To reduce the size, build pipeline can be split into multiple stages, where the final image only contains built binaries. This article shows how to set up a Docker image with services developed in Go, maintain multiple services inside a single image and how to use the image with Kubernetes.


Install kubectl, Docker, and Minikube to run Kubernetes locally.

Run Minikube, or configure kubectl for some other provider.

minikube start [--vm-driver=<driver>]

Developing services

Install golang/dep, a tool for dependency management. Using it will simplify building Docker images.

go get -u

Create first directory, and initialize dep inside it.

mkdir first && cd first
dep init

Create first/main.go, and write a simple HTTP server.

package main

import (

func main() {
  r := http.NewServeMux()
  r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "first")
  http.ListenAndServe(":3000", r)

Create another service inside second directory, which returns a string "second".

You can develop any number of services in a similar fashion. Each in a separate subdirectory.

Build "all-in-one" image

Create a Dockerfile inside project's root directory.

# Build stage
FROM golang:1.9.2-alpine3.6 AS build
# Support CGO and SSL
RUN apk --no-cache add gcc g++ make ca-certificates
WORKDIR /go/src/app
# Copy each service
COPY first first
COPY second second
# Compile them
RUN go install ./...

# Production build stage
FROM alpine:3.6
WORKDIR /usr/bin
# Copy built binaries
COPY --from=build /go/bin .

Each FROM instruction begins a new stage. The first stage is named "build". In the final stage, binaries get copied from the first stage by setting the --from=build.

For this example, the image will be pushed to Docker Hub.

Log-in with your Docker account.

docker login

Run the following command; replacing <username> with your Docker Hub username and <image> with repository's name.

docker build -t <username>/<image>

Push it to Docker Hub.

docker push <username>/<image>

Deploy to Kubernetes

Create first.yaml file; replacing <username> and <image>.

The spec.containers[].command field sets the default executable, in this case "first". To run another service, you'd simply change this field to some other service, which was compiled in the same Docker image.

apiVersion: extensions/v1beta1
kind: Deployment
  name: tutorial
    app: tutorial
      app: tutorial
  replicas: 3
        app: tutorial
      - name: tutorial
        command: ["first"]
        - containerPort: 3000
apiVersion: v1
kind: Service
  name: tutorial
  type: LoadBalancer
    app: tutorial
  - port: 3000

Create resources.

kubectl apply -f first.yaml

Try calling the service.

curl $(minikube service tutorial --url)

Wrapping up

Using multi-stage Docker builds can help you drastically reduce the size of Docker images. It's also helpful to keep multiple services inside a single image, because it makes your build pipeline easier to maintain.


Get awesome articles delivered right to your doorstep

Protected by reCAPTCHA - Privacy - Terms