Skip to main content

Docker & Containerization — Go Application Deployment

Go applications compile to a single binary making them ideal for containerization. Using minimal base images (scratch, distroless) results in images of just a few MB.


Basic Dockerfile

# Dockerfile
FROM golang:1.22-alpine AS builder

WORKDIR /app

# Dependency caching (prevent re-download on code changes)
COPY go.mod go.sum ./
RUN go mod download

# Copy source code and build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-o server \
./cmd/server

# Final image — scratch (empty image)
FROM scratch

# SSL certificates (needed for HTTPS requests)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Timezone data (if needed)
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

# Copy binary only
COPY --from=builder /app/server /server

EXPOSE 8080

ENTRYPOINT ["/server"]

Build & Run

# Build image
docker build -t myapp:latest .

# Check build result (scratch images are just a few MB)
docker images myapp

# Run
docker run -p 8080:8080 \
-e DATABASE_URL="postgres://..." \
-e REDIS_URL="redis://..." \
myapp:latest

Multi-Stage Optimized Dockerfile

# Dockerfile.production
FROM golang:1.22-alpine AS builder

# Install build tools
RUN apk add --no-cache git make

WORKDIR /app

# Module caching layer
COPY go.mod go.sum ./
RUN go mod download && go mod verify

# Copy entire source
COPY . .

# Inject version info
ARG VERSION=unknown
ARG BUILD_TIME=unknown

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w \
-X main.version=${VERSION} \
-X main.buildTime=${BUILD_TIME}" \
-trimpath \
-o /bin/server \
./cmd/server

# Health check binary
RUN go build -o /bin/healthcheck ./cmd/healthcheck

# ---- Final image ----
# distroless: slightly larger than scratch but includes debugging tools
FROM gcr.io/distroless/static-debian12

COPY --from=builder /bin/server /server
COPY --from=builder /bin/healthcheck /healthcheck

# Run as non-root user (security)
USER nonroot:nonroot

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/healthcheck"]

ENTRYPOINT ["/server"]

docker-compose — Local Development Environment

# docker-compose.yml
version: '3.8'

services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://user:password@postgres:5432/mydb?sslmode=disable
- REDIS_URL=redis://redis:6379
- APP_ENV=development
volumes:
- .:/app # Volume mount for hot reload
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network

postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d # Init SQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 5
networks:
- app-network

redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-network

# Development DB management tool
adminer:
image: adminer
ports:
- "8081:8080"
networks:
- app-network

volumes:
postgres-data:
redis-data:

networks:
app-network:
driver: bridge

Development Dockerfile (Hot Reload)

# Dockerfile.dev
FROM golang:1.22-alpine

RUN go install github.com/air-verse/air@latest

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

EXPOSE 8080

CMD ["air", "-c", ".air.toml"]
# .air.toml — air configuration
root = "."
tmp_dir = "tmp"

[build]
cmd = "go build -o ./tmp/server ./cmd/server"
bin = "./tmp/server"
include_ext = ["go"]
exclude_dir = ["vendor", "tmp"]
delay = 1000

[log]
time = true
# Start development environment
docker compose up

# Run in background
docker compose up -d

# View logs
docker compose logs -f app

# Rebuild specific service only
docker compose up --build app

# Shutdown and remove volumes
docker compose down -v

Multi-Service docker-compose

# docker-compose.microservices.yml
version: '3.8'

services:
user-service:
build: ./services/user
ports: ["8081:8080"]
environment:
- DATABASE_URL=postgres://user:pass@postgres:5432/users
depends_on: [postgres]

order-service:
build: ./services/order
ports: ["8082:8080"]
environment:
- DATABASE_URL=postgres://user:pass@postgres:5432/orders
- USER_SERVICE_URL=http://user-service:8080
depends_on: [postgres, user-service]

api-gateway:
build: ./services/gateway
ports: ["8080:8080"]
environment:
- USER_SERVICE_URL=http://user-service:8080
- ORDER_SERVICE_URL=http://order-service:8080
depends_on: [user-service, order-service]

postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: pass
volumes:
- ./init-db.sql:/docker-entrypoint-initdb.d/init.sql

kafka:
image: confluentinc/cp-kafka:latest
ports: ["9092:9092"]
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181

.dockerignore

# .dockerignore
.git
.github
.gitignore
*.md
Makefile
docker-compose*.yml
**/*_test.go
**/testdata/
tmp/
dist/
coverage.out

Makefile Docker Targets

IMAGE_NAME = myapp
VERSION = $(shell git describe --tags --always)
BUILD_TIME = $(shell date -u +%Y-%m-%dT%H:%M:%SZ)

.PHONY: docker-build docker-push docker-run

docker-build:
docker build \
--build-arg VERSION=$(VERSION) \
--build-arg BUILD_TIME=$(BUILD_TIME) \
-t $(IMAGE_NAME):$(VERSION) \
-t $(IMAGE_NAME):latest \
.

docker-push:
docker push $(IMAGE_NAME):$(VERSION)
docker push $(IMAGE_NAME):latest

docker-run:
docker run --rm -p 8080:8080 $(IMAGE_NAME):latest

# Development environment
dev-up:
docker compose up -d

dev-down:
docker compose down

dev-logs:
docker compose logs -f

Key Takeaways

TechniqueEffect
Multi-stage buildExclude build tools from final image
scratch base5-15MB images
distroless basescratch + TLS certificates
CGO_ENABLED=0Pure Go binary (no glibc dependency)
-s -w ldflagsRemove debug symbols (~30% size reduction)
USER nonrootRun without root privileges (security)
HEALTHCHECKAutomatic container health monitoring