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
- Multistage builds are used.
- UPX used to reduce binary size
- Running Go Infrastructure within docker image.
#Read More
- https://ardanlabs.com/blog/2020/02/docker-images-part1-reducing-image-size.html
- https://ardanlabs.com/blog/2020/02/docker-images-part2-details-specific-to-different-languages.html
#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"
"os"
"github.com/gofiber/fiber/v3"
)
const AppPort = "8080"
func setupApp() *fiber.App {
app := fiber.New()
app.Get("/ping", func(c fiber.Ctx) error {
return c.SendString("pong")
})
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
return app
}
func port() string {
port := os.Getenv("APP_PORT")
if port != "" {
return port
}
return AppPort
}
func main() {
app := setupApp()
if err := app.Listen(":" + port()); err != nil {
log.Fatal(err)
}
}
// - docker-compose.yaml -
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
app_distroless:
image: "butuzov/goapp-distroless:latest"
build:
context: .
dockerfile: distroless.Dockerfile
ports:
- 8091:8090
environment:
APP_PORT: 8090
command: /main
networks:
- docker-network
networks:
docker-network:
driver: bridge
# - alpine.Dockerfile -
ARG BUILDPLATFORM=linux/amd64
# Prepare Builder Platrform
FROM --platform=${BUILDPLATFORM} golang:1.26.1-alpine AS builder
ENV CGO_ENABLED=0
ENV GOARCH=amd64
ENV GOOS=linux
ENV GOPATH=/root/go
RUN apk add --no-cache curl git bash make tzdata ca-certificates
COPY --from=golangci/golangci-lint:v2.11-alpine /usr/bin/golangci-lint /usr/bin
RUN go install github.com/go-delve/delve/cmd/dlv@v1.26.1
# Build Process
FROM builder AS build
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . /build
# Build & Lint & Test
RUN go build -ldflags "-w -s" -o /main
RUN go test ./...
RUN golangci-lint run ./...
# RUN apk update && \
# apk add upx && \
# upx --brute /main
# Final scratch image
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo/
COPY --from=build /main /
COPY --from=build /root/go/bin/dlv /bin/
ENV APP_PORT=8090
EXPOSE $APP_PORT
WORKDIR /
# CMD [ "/main" ]
CMD [ "/bin/dlv", "--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "/main", "--continue"]
# - debian.Dockerfile -
ARG BUILDPLATFORM=linux/amd64
# --- Builder ------------------------------------------------------------------
FROM --platform=${BUILDPLATFORM} golang:1.26.1-bookworm AS builder
ENV CGO_ENABLED=0
ENV GOARCH=amd64
ENV GOOS=linux
ENV GOPATH=/root/go
RUN go install github.com/go-delve/delve/cmd/dlv@v1.26.1
COPY --from=golangci/golangci-lint:v2.11 /usr/bin/golangci-lint /usr/bin
# --- Build --------------------------------------------------------------------
FROM builder AS build
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . /build
# --- Code Quality -- Build & Lint & Test --------------------------------------
RUN go test ./...
RUN golangci-lint run ./...
RUN go build -o /main
# --- Packing it ---------------------------------------------------------------
RUN echo "deb http://deb.debian.org/debian bookworm-backports main" > /etc/apt/sources.list.d/backports.list && \
apt-get update && apt-get install -y -t bookworm-backports upx-ucl
RUN upx --brute /main
# --- Prod Ready Image ---------------------------------------------------------
FROM gcr.io/distroless/base-debian13
COPY --from=build /main /
COPY --from=build /root/go/bin/dlv /bin/
ENV APP_PORT=8090
EXPOSE $APP_PORT
WORKDIR /
# CMD [ "/main" ]
CMD [ "/bin/dlv", "--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "/main", "--continue"]
# - distroless.Dockerfile -
ARG BUILDPLATFORM=linux/amd64
# --- Builder ------------------------------------------------------------------
FROM --platform=${BUILDPLATFORM} golang:1.26.1-bookworm AS builder
ENV CGO_ENABLED=0
ENV GOARCH=amd64
ENV GOOS=linux
ENV GOPATH=/root/go
RUN go install github.com/go-delve/delve/cmd/dlv@v1.26.1
COPY --from=golangci/golangci-lint:v2.11 /usr/bin/golangci-lint /usr/bin
# --- Build --------------------------------------------------------------------
FROM builder AS build
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . /build
# --- Code Quality -- Build & Lint & Test --------------------------------------
RUN go test ./...
RUN golangci-lint run ./...
RUN go build -o /main
# --- Packing it ---------------------------------------------------------------
# RUN echo "deb http://deb.debian.org/debian bookworm-backports main" > /etc/apt/sources.list.d/backports.list && \
# apt-get update && apt-get install -y -t bookworm-backports upx-ucl
# RUN upx --brute /main
# --- Prod Ready Image ---------------------------------------------------------
FROM gcr.io/distroless/static:nonroot
COPY --from=build /main /
COPY --from=build /root/go/bin/dlv /bin/
ENV APP_PORT=8090
EXPOSE $APP_PORT
WORKDIR /
CMD [ "/main" ]
CMD [ "/bin/dlv", "--listen=:2345", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "/main", "--continue"]