How to Structure Go Projects with Docker and Kubernetes

Structure Go projects using the golang-standards/project-layout by placing entry points in cmd/, private logic in internal/app/, public libraries in pkg/, Docker assets in build/package/, and Kubernetes manifests in deployments/ to maintain clean separation between source code, containerization, and orchestration.

The golang-standards/project-layout repository provides an idiomatic blueprint for organizing Go code that scales naturally into containerized environments. By following these directory conventions, you create a predictable pipeline from local go build to Docker images to Kubernetes deployments without cluttering your application logic with infrastructure concerns.

Core Directory Layout for Containerized Applications

The foundation of a Docker- and Kubernetes-ready Go project rests on four key directories that separate binaries, implementation details, public APIs, and container assets.

Command Entry Points (cmd/)

Each application binary lives in its own subdirectory under cmd/. The golang-standards/project-layout specifies that cmd/<app>/main.go serves as the entry point that wires together packages from internal/ and pkg/.

// cmd/api/main.go
package main

import (
    "log"
    "myproject/internal/app"
)

func main() {
    if err := app.Run(); err != nil {
        log.Fatalf("server failed: %v", err)
    }
}

This pattern allows multiple binaries (e.g., cmd/api/, cmd/worker/) to coexist in one repository, each building into a separate container image.

Private Application Code (internal/app/)

Place your business logic and service implementations in internal/app/<your_app>/. The internal/ prefix enforces the Go compiler's visibility rules, preventing external projects from importing these packages. This directory contains the HTTP handlers, database repositories, and domain logic that your cmd/ binaries invoke.

Public Libraries (pkg/)

Reusable packages intended for consumption by other projects belong in pkg/<your_public_lib>/. Unlike internal/, these packages are importable by external modules and should maintain stable APIs. Use this directory for utility libraries, middleware, or domain packages shared across multiple microservices.

Containerization with Docker (build/package/)

According to the repository guidelines, Docker-related assets reside in build/package/ (or alternatively at the repository root). This keeps build scripts, Dockerfiles, and container configurations isolated from source code.

Multi-Stage Dockerfile

Create a Dockerfile in build/package/ that compiles your Go binary in a builder stage and copies it into a minimal runtime image:


# build/package/Dockerfile

FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /bin/api ./cmd/api

FROM gcr.io/distroless/static-debian12
COPY --from=builder /bin/api /api
EXPOSE 8080
ENTRYPOINT ["/api"]

Build the image from the repository root with:

docker build -f build/package/Dockerfile -t myorg/api:latest .

Docker Compose for Local Stacks

The deployments/ directory (detailed below) also accommodates local orchestration files. Place a docker-compose.yml there to spin up dependencies alongside your application:


# deployments/docker-compose.yml

version: "3.8"
services:
  api:
    build:
      context: ..
      dockerfile: build/package/Dockerfile
    ports:
      - "8080:8080"
    volumes:
      - ../configs/config.yaml:/etc/api/config.yaml:ro
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: devpassword

Kubernetes Orchestration (deployments/)

The deployments/ directory houses all infrastructure-as-code templates, including Helm charts, raw Kubernetes manifests, Kustomize overlays, and Terraform modules. This centralizes deployment logic and allows CI/CD pipelines to target a single directory for all environments.

Helm Chart Structure

Organize Kubernetes resources using Helm under deployments/helm/<chart>/:


# deployments/helm/api/Chart.yaml

apiVersion: v2
name: api
description: Go API service
version: 0.1.0
appVersion: "1.0"

# deployments/helm/api/values.yaml

replicaCount: 3
image:
  repository: myorg/api
  tag: latest
  pullPolicy: IfNotPresent
configPath: /etc/api/config.yaml

# deployments/helm/api/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
      - name: api
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: config
          mountPath: /etc/api
      volumes:
      - name: config
        configMap:
          name: {{ .Chart.Name }}-config

Configuration Management (configs/)

External configuration files belong in configs/. Mount these into Kubernetes pods as ConfigMaps or Secrets, keeping sensitive data out of your container images:

kubectl create configmap api-config --from-file=configs/config.yaml

Connecting the Layers: From Source to Cluster

The complete workflow follows this path:

  1. Develop business logic in internal/app/ and utilities in pkg/
  2. Expose functionality through cmd/api/main.go which imports your internal packages
  3. Build the container using build/package/Dockerfile, which compiles the cmd/api binary
  4. Deploy using manifests in deployments/helm/ or raw YAML that reference the image built in step 3
  5. Configure via files in configs/ mounted as ConfigMaps in Kubernetes

This structure ensures that changing your HTTP router requires no modifications to Docker or Kubernetes files, and scaling replicas in Kubernetes requires no changes to your Go source.

Summary

  • Place entry points in cmd/<app>/main.go to produce specific binaries for containerization
  • Isolate private logic in internal/app/ and public libraries in pkg/ to control API boundaries
  • Store Docker configurations in build/package/ to keep container build logic separate from source code
  • Centralize deployment manifests in deployments/ for Helm charts, Kubernetes YAML, and Terraform
  • Manage configuration files in configs/ for easy mounting as ConfigMaps or Secrets in Kubernetes
  • Use multi-stage Docker builds to compile Go binaries in a full toolchain image and run them in distroless containers

Frequently Asked Questions

Where should I put the Dockerfile in a Go project?

According to the golang-standards/project-layout repository, Docker-related files belong in build/package/ (as documented in [build/README.md](https://github.com/golang-standards/project-layout/blob/master/build/README.md)). This directory may contain Dockerfiles, build scripts, and packaging metadata. Alternatively, you may place a Dockerfile at the repository root, but build/package/ remains the preferred location for complex projects with multiple container images.

What is the difference between internal/ and pkg/ directories?

The internal/ directory contains packages that are implementation details specific to your application; the Go compiler enforces that code outside your module's tree cannot import these packages. Use internal/app/ for your service layer and business logic. The pkg/ directory contains packages intended for external consumption, such as client libraries or utility packages that other projects might import via Go modules. This distinction protects your internal APIs while advertising stable public interfaces.

How do I handle environment-specific configuration in Kubernetes deployments?

Store template configuration files in the configs/ directory (as described in [configs/README.md](https://github.com/golang-standards/project-layout/blob/master/configs/README.md)). During deployment, load these files into Kubernetes as ConfigMaps or Secrets using kubectl create configmap or Helm's Files.Get function. Mount them into your containers at paths defined in your deployments/ manifests, allowing the same container image to run in development, staging, and production by changing only the mounted configuration.

Can I use docker-compose for local development with this layout?

Yes. Place docker-compose.yml files in the deployments/ directory alongside your Kubernetes templates, as the deployments/ folder is intended for "container orchestration and deployment configurations" including Docker Compose, Helm, and Terraform. Reference the Dockerfile in build/package/ using the dockerfile build context option to ensure local builds use the same multi-stage pipeline as your CI/CD system.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →