เพชรบล็อก

  • Website Tech Stack

Building Docker Images with Private Go Modules: A Secure Approach

coding

14 มีนาคม 2569


Introduction

In modern Go development, we often rely on private repositories to share proprietary code and internal packages. But when it comes to building Docker images for these applications, authentication becomes a critical challenge. How do you give your Docker build access to private GitHub repositories without exposing sensitive credentials in your final image?

The Problem: Private Dependencies in Containerized Go Apps

Imagine you're building a Go application that depends on internal packages hosted in private GitHub repositories. Your go.mod file might look something like this:

module your-company/awesome-service

require (
    github.com/your-company/internal-auth v1.2.3
    github.com/your-company/shared-utils v0.8.1
)

When you try to build this with a standard Dockerfile, you'll hit a wall:

FROM golang:1.26.1
WORKDIR /app
COPY . .
RUN go build -o app cmd/main.go

The build fails with "module not found" errors because Docker can't authenticate to access your private repositories.

The Traditional (Insecure) Solutions

Many developers resort to problematic workarounds:

  1. Embedding tokens in Dockerfile: ENV GITHUB_TOKEN=ghp_... - Terrible security practice
  2. Multi-stage builds with secrets: Better, but complex to manage
  3. SSH key mounting: Requires additional configuration and key management

These approaches either compromise security or add unnecessary complexity to your build process.

The Modern Solution: Docker Secret Mounts

Docker BuildKit introduced a game-changing feature: secret mounts. This allows you to securely inject secrets during the build process without persisting them in the final image.

Here's how it works:

FROM golang:1.26.1
WORKDIR /app
COPY . .

RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \
    git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/your-company".insteadOf "https://github.com/your-company"

RUN go build -o app cmd/main.go
CMD ["./app"]

Step-by-Step Implementation

1. Create a GitHub Personal Access Token

  • Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
  • Generate a new token and select Generate new token (classic)
  • Ensure the repo scope is checked (this allows access to private repositories)
  • Treat this token like a password - never commit it to version control

2. Configure Your Dockerfile

The key is the --mount=type=secret directive:

RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \
    command-that-needs-the-token

This mounts the secret as an environment variable only during this specific RUN command.

3. Set Up Git Authentication

RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \
    git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/your-company".insteadOf "https://github.com/your-company"

This tells Git to use your token for authentication when accessing your organization's repositories.

4. Build with the Secret

DOCKER_BUILDKIT=1 docker build \
  --secret id=GITHUB_TOKEN,env=$GITHUB_TOKEN \
  -t your-app:latest \
  .

Production-Ready Example

Here's a complete, production-ready Dockerfile with error handling:

FROM golang:1.26.1 AS builder
WORKDIR /app

# Validate that the token is available
RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \
    if [ -z "$GITHUB_TOKEN" ]; then \
        echo "❌ GITHUB_TOKEN not provided - build cannot continue" && exit 1; \
    fi

# Configure git for private repositories
RUN --mount=type=secret,id=GITHUB_TOKEN,env=GITHUB_TOKEN \
    git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/your-company".insteadOf "https://github.com/your-company"

# Download dependencies
COPY go.mod go.sum ./
RUN go mod download

# Build the application
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app cmd/main.go

# Runtime stage - no secrets here!
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
CMD ["./app"]

Common Errors and Solutions

When building Docker images with private Go modules, you might encounter these frequent issues. Understanding these errors will help you debug your build process faster.

1. Error: "module not found: git ls-remote... terminal prompts disabled"

This is the most common error. It indicates that go mod download is attempting to fetch a private repository but Git cannot authenticate. Solution:

  • Double-check your git config --global url...insteadOf mapping in the Dockerfile.
  • Ensure the GITHUB_TOKEN is correctly mounted using --mount=type=secret.

2. Error: "could not read Username for 'https://github.com': terminal prompts disabled"

This happens when Go doesn't know the repository is private and tries to fetch it via public channels. Solution: Set the GOPRIVATE environment variable in your Dockerfile to bypass the Go proxy for your internal repositories:

ENV GOPRIVATE=github.com/your-company/*

3. Error: "failed to load cache key: secret GITHUB_TOKEN not found"

This error occurs at the start of the build when Docker cannot find the secret you referenced in your Dockerfile. Solution: Ensure you are passing the secret in your build command:

docker build --secret id=GITHUB_TOKEN,env=GITHUB_TOKEN .

4. Error: "401 Unauthorized"

The token provided is either invalid, expired, or lacks the necessary scopes. Solution: Create a new Personal Access Token (PAT) with the repo scope and ensure it has been authorized for use with your organization's SAML SSO if applicable.

Conclusion

Docker secret mounts provide a secure, elegant solution for building Go applications with private dependencies. By following these practices, you can:

  • ✅ Keep your credentials secure
  • ✅ Build reproducible images
  • ✅ Simplify your CI/CD pipelines
  • ✅ Maintain compliance with security policies

The next time you're building a Go application with private dependencies, give Docker secret mounts a try. Your security team will thank you, and your build process will be cleaner and more maintainable.


This article is partially AI-generated

ผู้เขียน:

Kiart Tantasi (เพชร)



GitHub:

kiart-tantasi

เพชรบล็อก - [email protected]