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:
- Develop business logic in
internal/app/and utilities inpkg/ - Expose functionality through
cmd/api/main.gowhich imports your internal packages - Build the container using
build/package/Dockerfile, which compiles thecmd/apibinary - Deploy using manifests in
deployments/helm/or raw YAML that reference the image built in step 3 - 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.goto produce specific binaries for containerization - Isolate private logic in
internal/app/and public libraries inpkg/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →