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