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"
"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" ]