Go Dockerfile with Alpine Runtime: Multi-Stage, Non-Root
This Go Dockerfile builds a static binary on golang:1.25-alpine and runs it on a small alpine:3.22 image. The runtime has a shell, CA certificates and time zone data, and a dedicated non-root user. Choose it when you want a tiny image but still need to exec into the container to debug. The service listens on port 8080.
- CI status
- Build passing
- Image size (uncompressed)
- 14.8 MB
- Build time
- 30 s
- Runtime image
alpine:3.22- Port
- 8080
- Last built
- 2026-10-15
# syntax=docker/dockerfile:1
FROM golang:1.25-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/server
FROM alpine:3.22
RUN apk add --no-cache ca-certificates tzdata \
&& adduser -D -u 10001 app
COPY --from=build /out/app /usr/local/bin/app
USER app
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/app"].git
bin/
*.test
.env
Dockerfile
.dockerignoreBefore you build
- Put your main package in ./cmd/server, or adjust the path in the go build line.
- Build without cgo (CGO_ENABLED=0), unless you also add the C libraries your code needs to the runtime stage.
- Make the server listen on :8080 or read the port from an environment variable.
- Commit go.mod and go.sum so module downloads are cached between builds.
How this Dockerfile works
FROM golang:1.25-alpine AS buildThe full Go toolchain lives only in this stage. Nothing from it is shipped except the compiled binary.
COPY go.mod go.sum ./ RUN go mod downloadDependencies are downloaded in their own cached layer before the source code is copied, so code edits rebuild quickly.
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/serverProduces a static, stripped binary with no local paths embedded. Being static, it runs on any Linux base image regardless of the C library.
FROM alpine:3.22Alpine is one of the smallest general-purpose bases. Unlike scratch or distroless, it includes a shell and the apk package manager for debugging and extra tools.
RUN apk add --no-cache ca-certificates tzdata \Adds the certificate bundle for outgoing HTTPS and the time zone database. --no-cache keeps apk's index out of the image.
&& adduser -D -u 10001 appCreates a passwordless user with a fixed UID in the same layer, so file ownership stays predictable across image rebuilds.
COPY --from=build /out/app /usr/local/bin/appPlaces the binary on the PATH, so you can also run it by name when you exec into the container.
USER app EXPOSE 8080 ENTRYPOINT ["/usr/local/bin/app"]Drops root privileges, documents the listening port, and starts the binary as the main process so it receives stop signals.
Build and run
docker build -t go-app .
docker run --rm -p 8080:8080 go-appRun the build from the module root. Because Alpine has a shell, docker exec -it <container> sh lets you inspect files, environment variables and network settings while it runs.
Other Go Dockerfile variants
Alpine trades a few extra megabytes for a shell and package manager, which is handy while debugging. Distroless static and scratch are smaller and expose less, but you can't open a shell inside them. The table below compares the measured sizes of all three.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| Alpine runtime (shell available) | alpine:3.22 | 14.8 MB | Passing |
| Distroless static (nonroot) | gcr.io/distroless/static-debian12:nonroot | 7.7 MB | Passing |
| Scratch (static, nonroot UID) | scratch | 6.2 MB | Passing |
Troubleshooting
- exec /usr/local/bin/app: no such file or directory
- The binary was built with cgo against glibc, which Alpine doesn't use. Keep CGO_ENABLED=0, or install the C libraries your build needs in both stages.
- x509: certificate signed by unknown authority
- The ca-certificates package is missing from the runtime stage. Keep it in the apk add line so outgoing TLS connections can be verified.
- permission denied when the app writes a file
- The app user can't write to root-owned paths. Write to a directory you create and chown to app in the Dockerfile, or mount a volume.
FAQ
- When should I choose Alpine over distroless for Go?
- Pick Alpine when you or your platform need a shell inside the container, for example for debugging, init scripts or extra command-line tools. Pick distroless when minimal attack surface matters most.
- Does Alpine's musl libc affect Go?
- Not for static binaries built with CGO_ENABLED=0. They contain their own implementations and don't call into the C library. musl only matters when cgo is enabled.
- How do I add curl for health checks?
- Add curl to the apk add line in the runtime stage. Many orchestrators can probe the HTTP port directly, though, which avoids adding tools to the image.