Building Docker Images

I this example we are building a multistage small image for go binary. This is usually a goto example on devops classes/tutorials etc.

#Keypoints

  1. Multistage builds are used.
  2. UPX used to reduce binary size
  3. Running Go Infrastructure within docker image.

#Read More

#Examples

//  - Makefile - 
VERSION ?= 1.16


# ~~~ Build ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

version:              ## Prints Go version we going to work with.
	echo $(VERSION)

bases-pull:           ## Downlaods Base Images
	@docker pull golang:$(VERSION)-alpine
	@docker pull golang:$(VERSION)-buster


bases-list:            ## List Base Images
	@docker images | grep golang

buster:  # Build Debian Buster Image
	docker build -t butuzov/goapp-debian -f debian.Dockerfile .

distroless: # Build Distroless Image
	docker build -t butuzov/goapp-distroless -f distroless.Dockerfile .

alpine:        ## Build Alpine Image
	docker build -t butuzov/goapp-alpine -f alpine.Dockerfile .


all: alpine buster distroless ## Build all images

cleanfailed:          ## Clean untagged images
	@docker images -q -f "dangling=true" | xargs -L1 docker rmi
	@docker ps -q -f "status=exited" | xargs -L1 docker rm


# ~~~ Docker Compose ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

up: # Startup Environment
	docker-compose up --no-recreate -d

down: # Shutdown Environment
	docker-compose down -v

logs: # See Logs
	docker-compose logs

ls:  # Show resulted images
	@docker images | grep "butuzov/goapp"

clean: down cleanfailed    ## Teardown All Bases/Failed/Resulted
	@ echo "Cleanup Docker Images"
	@ docker images | grep "butuzov/goapp" | awk '{print $$3}' | xargs -L1 docker rmi
	@ @docker images | grep "golang" | awk '{print $$3}' | xargs -L1 docker rmi
//  - main.go - 
package main

import (
	"log"
	"net/http"
	"os"
)

const AppPort = "8080"

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Requested: %s", r.RequestURI)
		w.Write([]byte("ok"))
	})

	http.HandleFunc("/ping", func(w http.ResponseWriter, _ *http.Request) {
		log.Printf("Pong")
		w.Write([]byte("pong"))
	})

	port := port()
	log.Printf("Preparing to run on :%s", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

func port() string {
	port := os.Getenv("APP_PORT")
	if port != "" {
		return port
	}
	return AppPort
}
//  - docker-compose.yaml - 
version: '3.1'

services:

  app_alpine:
    image: "butuzov/goapp-alpine:latest"
    build:
      context: .
      dockerfile: alpine.Dockerfile
    ports:
      - 8093:8091
    environment:
      APP_PORT: 8091
    networks:
      - docker-network

  app_buster:
    image: "butuzov/goapp-debian:latest"
    build:
      context: .
      dockerfile: debian.Dockerfile
    ports:
      - 8092:8091
    environment:
      APP_PORT: 8091
    networks:
      - docker-network

networks:
  docker-network:
    driver: bridge
# - alpine.Dockerfile - 
FROM golang:1.16-alpine as BUILD


WORKDIR /build
COPY  . /build

RUN go mod download
# RUN GODEBUG=gocachehash=1 go test ./... -v
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux \
    go build \
       -ldflags "-w -s" \
       -o /main

RUN apk update && \
    apk add upx && \
    upx --brute /main


FROM golang:1.15-alpine as EXTRA
RUN apk update && apk add ca-certificates tzdata


# Final scratch image
FROM scratch

COPY --from=EXTRA /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=EXTRA /usr/share/zoneinfo /usr/share/zoneinfo/
COPY --from=BUILD /main /

ENV APP_PORT 8090
EXPOSE $APP_PORT

WORKDIR /
CMD [ "/main" ]
# - debian.Dockerfile - 
FROM golang:1.16-buster as BUILD

WORKDIR /build
COPY  . /build

RUN go mod download
RUN go test ./... -v

RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux \
    go build \
       -ldflags "-w -s" \
       -o /main

RUN apt-get update  \
    && apt-get install upx-ucl -y \
    && upx --brute /main

# We woundn't use scratch iamge, but distroless base debian.
FROM gcr.io/distroless/base-debian10
COPY --from=BUILD /main /

ENV APP_PORT 8090
EXPOSE $APP_PORT

WORKDIR /
CMD [ "/main" ]
# - distroless.Dockerfile - 
FROM golang:1.16-buster as BUILD

WORKDIR /build
COPY  . /build

RUN go mod download
RUN go test ./... -v

RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux \
    go build \
       -ldflags "-w -s" \
       -o /main

RUN apt-get update  \
    && apt-get install upx-ucl -y \
    && upx --brute /main

# We woundn't use scratch iamge, but distroless base debian.
FROM gcr.io/distroless/static:nonroot
COPY --from=BUILD /main /

ENV APP_PORT 8090
EXPOSE $APP_PORT

WORKDIR /
CMD [ "/main" ]