WarpBuild LogoWarpBuild Docs

Go with Go Modules

Best practices for Dockerfile for Go with Go Modules

🐳 Annotated Dockerfile for Go with Go Modules:

# Start with the official Go image as our builder
FROM golang:1.24-bookworm AS builder
 
# Set the working directory inside the container
WORKDIR /app
 
# Copy go.mod and go.sum files first for better caching
COPY go.mod go.sum ./
 
# Download dependencies using go modules
# This layer will be cached unless go.mod/go.sum changes
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go mod download
 
# Copy the source code into the container
COPY . .
 
# Build the application with optimizations
# CGO_ENABLED=0 creates a static binary
# -ldflags="-s -w" strips debug information to reduce binary size
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /go/bin/app
 
# --------------------------------------
# Stage 2: Create an extremely small runtime image
# --------------------------------------
FROM gcr.io/distroless/static-debian12
 
# Copy the binary from the builder stage
COPY --from=builder /go/bin/app /app
 
# Use a non-root user for better security (distroless provides 'nonroot')
USER nonroot:nonroot
 
# Expose the port the app runs on
EXPOSE 8080
 
# Command to run the executable
ENTRYPOINT ["/app"]

🔍 Why these are best practices:

✅ Multi-stage builds

  • Dramatically reduces final image size.
  • Eliminates all build dependencies and the Go compiler from the runtime image.
  • Final image contains only your statically compiled Go binary.

✅ Go Modules for dependency management

  • Ensures reproducible builds with explicit dependency versions.
  • go.mod and go.sum provide deterministic dependency resolution.
  • Downloads dependencies first to leverage Docker's caching.

✅ Caching Go modules and build cache

  • Uses Docker's build cache efficiently to avoid redundant downloads.
  • Significantly speeds up builds on iterative development.
  • Saves bandwidth and time, especially important in CI/CD environments.

✅ Static binary compilation

  • CGO_ENABLED=0 creates binaries with no external dependencies.
  • Allows use of scratch or distroless containers for maximum security.
  • Simplifies deployment across different environments.

✅ Binary optimization

  • Strips debug information to reduce binary size.
  • Smaller binaries mean faster container startup and smaller images.
  • Reduces attack surface by eliminating unnecessary information.

🚀 Additional Dockerfile best practices you can adopt:

Use scratch instead of distroless for even smaller images

If your application doesn't need certificates or other basics:

FROM scratch
 
# Copy necessary SSL certificates if your app makes HTTPS calls
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
 
COPY --from=builder /go/bin/app /app
 
ENTRYPOINT ["/app"]

Build for multiple architectures

For cross-platform compatibility:

# Build with platform-specific arguments
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /go/bin/app-amd64
 
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o /go/bin/app-arm64

Add build-time metadata with ldflags

Include version info and build timestamps:

ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_DATE=unknown
 
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build \
    -ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildDate=${BUILD_DATE}" \
    -o /go/bin/app

Vendor dependencies for air-gapped builds

For environments without internet access:

# First locally run: go mod vendor
COPY vendor/ ./vendor/
RUN --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build -mod=vendor -ldflags="-s -w" -o /go/bin/app

Use .dockerignore

Exclude unnecessary files from your Docker build context:

.git
.github
.gitignore
README.md
Dockerfile
docker-compose.yml
*.md
.idea
.vscode

Configure health checks

Ensure your container reports health correctly:

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

Enable Go's build-time security checks

For enhanced security scanning during builds:

# Enable Go's security-focused static analysis
RUN go install golang.org/x/vuln/cmd/govulncheck@latest && \
    govulncheck ./...

By following these practices, you'll create Docker images for your Go applications that are secure, efficient, and optimized for production environments. Go's strengths in producing small, statically-linked binaries make it an excellent language for containerized deployments.

Last updated on