---
title: "Best Dockerfile for Golang, Optimize Your Dockerfile"
description: "Create best Dockerfile for Golang, optimize your Dockerfile for Golang and make it blazingly fast! 🔥"
date: "2024-06-06"
updated: "2025-03-25"
oldPath: "02-golang-dockerfile"
tags:
  - "docker"
  - "golang"
  - "dockerfile"
  - "optimization"
  - "best-practices"
  - "performance"
  - Optimized Dockerfile for Golang
  - Best Dockerfile practices for Golang
  - Golang Dockerfile optimization techniques
  - Multi-stage Dockerfile build Golang
  - Reduce Docker image size Golang
  - Dockerfile performance tips Golang
  - Golang containerization best practices
  - Lightweight Docker image for Golang
  - Golang Dockerfile build process
  - Efficient Dockerfile for Go applications
  - Speed up Golang Docker builds
  - Dockerfile improvements for Go
  - Minimal Docker image for Golang
  - Golang Docker multi-stage builds
  - Best Golang Dockerfile examples
  - Optimize Go app Docker build
  - Creating efficient Docker images for Golang
  - Golang Docker build optimization tips
  - Advanced Dockerfile for Golang
  - Reducing Docker build time for Go applications
faqs:
  - question: "Why use a multi-stage Dockerfile for Go?"
    answer: "It keeps the final image small and secure: compile in a builder stage, then copy only the resulting binary (and required files) into a minimal runtime stage."
  - question: "How can I speed up Go Docker builds?"
    answer: "Cache dependencies: copy `go.mod`/`go.sum` first, download modules, then copy the rest of the source. This preserves layer caching when code changes but dependencies don’t."
  - question: "Should I use `scratch` or `distroless` for the runtime image?"
    answer: "`scratch` is the smallest but can be inconvenient for certificates and debugging. Distroless (or a slim base) is a good balance for many production services."
  - question: "How do I handle CA certificates in minimal images?"
    answer: "If your app makes HTTPS requests, ensure CA certs are available in the runtime stage (either by copying them or using a base image that includes them)."
  - question: "What’s the easiest way to keep images reproducible?"
    answer: "Pin base image versions, keep build flags consistent, and prefer deterministic builds (same inputs → same artifact)."
ogImage: "/images/dockerfile-golang-og.png"
---

[Image: Best Dockerfile for Golang, Optimize Your Dockerfile](https://saybackend.com/blog/2024-jun-golang-dockerfile-optimized/)

## Introduction

Creating a Dockerfile for Golang to containerize your application is a good practice, but sometimes, you can optimize it even more by using various techniques that we will be discussing in this article.

## Generic Dockerfile

Generally, you will find this common Dockerfile for Golang:

> It's always recommended to use the latest version of Golang, (but don't use the latest tag, keep the version hardcoded)

```dockerfile
FROM golang:1.24-alpine

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN go build -o main .

CMD ["./main"]
```

### Build the Dockerfile

```bash
docker build -t golang-app .
```

[Image: Docker Build](https://saybackend.com/blog/2024-jun-golang-dockerfile-optimized/)

It's absolutely terrible image, 490MB, even with alpine. We will gradually optimize it.

### Run the Dockerfile

```bash
docker run -d -p 8080:8080 golang-app
```

[Image: Docker Run](https://saybackend.com/blog/2024-jun-golang-dockerfile-optimized/)

It's running nicely, at port 8080.

ping the healthcheck endpoint:

```bash
curl http://localhost:8080/health
```

we get a success response, so it's working.

```json
{ "status": "healthy" }
```

## Optimizing the Dockerfile

To optimize your Dockerfile, you can use multi-stage builds, which help reduce the final image size and improve build times. Here is an optimized version of the Dockerfile:

```dockerfile
FROM golang:1.24-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o main .

# Final stage
FROM alpine:latest

WORKDIR /root/

COPY --from=builder /app/main .

CMD ["./main"]
```

- Build the Dockerfile

```bash
docker build -t golang-app .
```

[Image: Optimized Build](https://saybackend.com/blog/2024-jun-golang-dockerfile-optimized/)

It's just 20.39 MB, what more can you ask for? From 490MB to 20.39MB, that's a 95% reduction in size, not only that, there is also caching of the dependencies, so the subsequent builds are faster.

### Explanation on the Dockerfile

- **Multi-stage builds**: By using multi-stage builds, we can separate the build environment from the runtime environment. This helps in keeping the final image lightweight.
- **Alpine base image**: Using `alpine:latest` as the base image for the final stage ensures a minimal footprint.
- **Environment variables**: Setting `CGO_ENABLED=0` and `GOOS=linux` ensures that the binary is statically linked and compatible with Linux.
- **Caching**: Using multi stage builds, we can use the Docker multi stage build cache to cache the dependencies and intermediate layers, which speeds up the build process.
- **LD Flags**: Using `-ldflags="-w -s"` to strip the binary of debug information and symbols, which helps in reducing the size of the binary.

## Additional Optimization Tips

### Use .dockerignore

Create a `.dockerignore` file to exclude unnecessary files from the build context, which can speed up the build process and reduce the image size.

```plaintext
# .dockerignore
.git
build
random_stuff
bin
dist
```

> Make sure only the files needed for the build are included in the build context. and the assets that are needed.

### Cache Dependencies

Leverage Docker's layer caching by copying `go.mod` and `go.sum` files first and running `go mod download` before copying the rest of the application files. This way, dependencies are only downloaded if they change.

### Minimize Layers

Combine multiple `RUN` commands into a single layer to reduce the number of layers in the final image.

```dockerfile
RUN apk add --no-cache git && \
    go mod download && \
    go build -o main .
```

**Note:**
  It does not matter much in our case if we combine RUN commands or not, I've
  just given it as an example.

### Use Scratch Base Image

For the smallest possible image, you can use the `scratch` base image. This is useful for simple applications that do not require any additional libraries.

```dockerfile
# syntax=docker/dockerfile:1
# Build stage
FROM golang:1.24-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o main .

# Final stage
FROM scratch

COPY --from=builder /app/main .

CMD ["./main"]
```

**Note:**
  Smallest is not always best, it's important to consider the trade-offs and
  choose the best base image for your application.

## Best Optimization for Production

The Alpine image is quite nice, and works well, but, it can start causing some problems because of how it's DNS is configured. Please read this blog post for more information: [Alpine Linux DNS Issues](https://martinheinz.dev/blog/92) by Martin Heinz.

Preferably, you should use the debian-slim image, which is a minimal version of Debian, and has no such problems + the rock solid base of Debian.

```dockerfile
# Stage 1: Install dependencies
FROM golang:1.24-bookworm AS deps

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

# Stage 2: Build the application
FROM golang:1.24-bookworm AS builder

WORKDIR /app

COPY --from=deps /go/pkg /go/pkg
COPY . .

# Enable them if you need them
# ENV CGO_ENABLED=0
# ENV GOOS=linux

RUN go build -ldflags="-w -s" -o main .

# Final stage: Run the application
FROM debian:bookworm-slim

WORKDIR /app

# Create a non-root user and group
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Copy the built application
COPY --from=builder /app/main .

# Change ownership of the application binary
RUN chown appuser:appuser /app/main

# Switch to the non-root user
USER appuser

CMD ["./main"]
```

[Image: Debian Build](https://saybackend.com/blog/2024-jun-golang-dockerfile-optimized/)

Using the debian-slim image, we get the image of size 131 MB, which isn't that much, and is super stable. Always prefer stability over the smallest size.

### Still want to use Alpine?

If you still want to use Alpine, update the final stage of dockerfile to this

```dockerfile
# Final stage: Run the application
FROM alpine:latest

WORKDIR /app

# Create a non-root user and group
RUN addgroup -S appuser && adduser -S appuser -G appuser

# Copy the built application
COPY --from=builder /app/main .

# Change ownership of the application binary
RUN chown appuser:appuser /app/main

# Switch to the non-root user
USER appuser

CMD ["./main"]
```

### Using github actions?

In case you are using github actions, build the docker binary there, so that you can leverage the existing cache using the setup-go action.

```yaml
name: Run CICD

on:
  push:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source
        uses: actions/checkout@v4
        with:
          fetch-depth: 1
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.24"
      - name: Build
        run: go build -ldflags="-w -s" -o main
      - name: Build Docker Image
        run: |
          docker build -t golang-app .
```

and set your Dockerfile to just the final stage, and rather than copying the binary from the builder stage, we will just copy the built binary from the github actions cache.

```dockerfile
# Final stage: Run the application
FROM debian:bookworm-slim

WORKDIR /app

# Create a non-root user and group
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Copy the built application
COPY ./main /app/main

# Change ownership of the application binary
RUN chown appuser:appuser /app/main

# Switch to the non-root user
USER appuser
CMD ["/main"]
```

## Conclusion

By following these optimization techniques, you can create a more efficient and faster Dockerfile for your Golang applications. This not only improves the performance but also reduces the size of your Docker images, making them easier to distribute and deploy.

**Note:**
  Remember, optimizing your Dockerfile is an ongoing process. Continuously
  monitor and refine your Dockerfile to ensure it meets the needs of your
  application and deployment environment.

**Warning:**
  **_Never add secrets or sensitive information directly in the Dockerfile. Use
  environment variables or Docker secrets for sensitive information._**
