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
| Technique | Effect |
|---|---|
| Multi-stage build | Exclude build tools from final image |
scratch base | 5-15MB images |
distroless base | scratch + TLS certificates |
CGO_ENABLED=0 | Pure Go binary (no glibc dependency) |
-s -w ldflags | Remove debug symbols (~30% size reduction) |
USER nonroot | Run without root privileges (security) |
| HEALTHCHECK | Automatic container health monitoring |