Back to all articles
Enterprise Systems2026-05-04

Microservices with Go (Golang) and Docker: Structure for Scale

Learn how to structure microservices using Go and containerization with Docker to build robust and fast distributed systems.

Microservices architecture has become the industry standard for large-scale applications, allowing teams to work independently and individual components to scale according to demand. The Go (Golang) language, created by Google, stands out in this scenario due to its low memory consumption, native concurrency (goroutines), and execution speed close to C.

In this article, we will detail how to organize and structure microservices in Go using Docker containers.

1. Repository Organization (Folder Structure)

Following a clean and standardized structure is essential for the project to remain readable as it grows. The recommended pattern for Go projects is as follows:

/my-microservice
├── cmd/
│   └── app/
│       └── main.go       # System entry point
├── internal/
│   ├── handler/          # HTTP or gRPC controllers
│   ├── model/            # Definition of entities and domain rules
│   └── repository/       # Direct communication with the database
├── pkg/                  # Shared code (utilities)
├── Dockerfile            # Container configuration
└── go.mod                # Go module manager

2. Writing the Optimized Dockerfile (Multi-stage Build)

For microservices, the size of the Docker image directly affects deploy time (CI/CD) and disk consumption on the cluster. In Go, we can use multi-stage build to generate final images of only a few megabytes.

Professional Dockerfile Example:

# Build Stage
FROM golang:1.22-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 ./cmd/app

# Final Executable Stage (Clean Image)
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]

With this strategy, we remove the Go compiler and all source code history from the final image, leaving only the compiled binary running on an extremely light and secure Alpine base.

3. Build the binary and check it

main.go in cmd/app only has to answer /health for the first deploy. The rest of the API goes in the internal/ packages.

package main

import "net/http"

func main() {
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})
	if err := http.ListenAndServe(":8080", nil); err != nil {
		panic(err)
	}
}

Compose publishes the port only on localhost. The final image is the one from the Dockerfile in section 2, without the compiler.

services:
  app:
    build: .
    ports:
      - "127.0.0.1:8080:8080"
docker compose up -d --build
curl -fsS http://127.0.0.1:8080/health
docker image ls

The response ok means the process is up. docker image ls shows the final image, without the builder tag. If the build fails on go.sum, run go mod tidy on the development machine and commit that file with go.mod.

Conclusion

Combining the native high performance of Go with the environment isolation provided by Docker is the ideal foundation for building scalable systems ready to run in Kubernetes clusters. This architecture guarantees low response times and efficient use of CPU and RAM resources on the server.

Corporate software engineering

Migration and custom systems while the operation stays online.

Talk to engineering