Golang with Docker custom integration involves combining the Go programming language environment with Docker containerization to create, build, test, and deploy Go applications in portable, isolated containers. This integration streamlines development and ensures consistent runtime environments. The following is a detailed explanation and a sample integration workflow covering key aspects such as project setup, Dockerfile creation, building images, running containers, and advanced practices like multi-stage builds and integration testing.
Setting up a Basic Golang Project
Start with a simple Go project. For example, create a folder named `go-docker-app`, initialize it as a Go module, and write a simple Go application and test.
1. Create a directory and initialize Go module:
bash
mkdir go-docker-app
cd go-docker-app
go mod init go-docker-app
2. Write a simple Go function in `main.go`:
go
package main
import "fmt"
func Add(x, y int) int {
return x + y
}
func main() {
fmt.Println("Sum is:", Add(2, 3))
}
3. Write a test file `main_test.go` for the `Add` function:
go
package main
import "testing"
func TestAdd(t *testing.T) {
if Add(2, 3) != 5 {
t.Fail()
}
}
This is a starting point for the Go application that you want to containerize with Docker.
Creating a Dockerfile for the Go Application
The Dockerfile instructs Docker how to build the image containing the Go app. Using the official Go base image simplifies the setup because it contains the Go toolchain.
Here is a straightforward `Dockerfile` example:
dockerfile
# Use official Golang image as the base image
FROM golang:1.19
# Create and set the working directory inside the container
WORKDIR /app
# Copy the go.mod and go.sum files to download dependencies
COPY go.mod go.sum ./
RUN go mod download
# Copy all source files into the container's workspace
COPY . .
# Build the Go application
RUN go build -o main .
# Define the command to run the binary on container start
CMD ["./main"]
Build the Docker image with the command:
bash
docker build -t go-docker-app .
Run the container:
bash
docker run --rm go-docker-app
You should see the output:
Sum is: 5
This confirms your Go application runs correctly inside the container.
Using Multi-Stage Builds for Smaller Production Images
To optimize image size, it's typical to use Docker multi-stage builds. This approach builds the Go app in a full Golang environment but copies only the resulting binary to a minimal base image for runtime, reducing the final image size drastically.
Example multi-stage Dockerfile:
dockerfile
# Build stage
FROM golang:1.19 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# Final stage with minimal image
FROM scratch
WORKDIR /app
# Copy the statically linked binary from the builder stage
COPY --from=builder /app/app .
# Run the binary
CMD ["./app"]
This builds a static binary with CGO disabled (`CGO_ENABLED=0`), making it executable in the minimal `scratch` image, which contains no package manager or shell, only the app binary.
Build and run as before:
bash
docker build -t go-docker-app .
docker run --rm go-docker-app
The resulting image is typically just a few megabytes.
Docker Compose for Complex Integration
When your Go application depends on other services such as a database, use `docker-compose` to orchestrate multi-container setups.
Example Compose file `docker-compose.yml`:
yaml
version: "3.8"
services:
app:
build: .
ports:
- "808080"
depends_on:
- db
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=example
- DB_NAME=mydb
db:
image: postgres:13
restart: always
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: example
POSTGRES_DB: mydb
ports:
- "5432:5432"
This runs both the Go application and a PostgreSQL instance, linking them via Docker's internal network.
Running Tests inside Docker Containers
Containerizing tests provides a consistent environment, especially for integration tests.
Example Dockerfile snippet to run tests:
dockerfile
FROM golang:1.19
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
CMD ["go", "test", "-v", "./..."]
Build and run:
bash
docker build -t go-test .
docker run --rm go-test
This facilitates isolation and reproducibility for tests across different environments.
Live Development with Docker
For development speed, you can set up Docker to support live reloading using tools such as `air` or `fresh`. This allows code changes on the host to trigger recompilation and restart the Go application inside the container.
Example Dockerfile stage for development with `air`:
dockerfile
FROM golang:1.19 as development
WORKDIR /app
RUN go install github.com/cosmtrek/air@latest
COPY go.mod go.sum ./
RUN go mod download
COPY . .
CMD ["air"]
Run the container mounting the current directory:
bash
docker run --rm -v $(pwd):/app -p 808080 go-docker-app:development
This approach gives a containerized development environment with fast feedback loops.
Best Practices and Optimization Tips
- Use `.dockerignore` to exclude unnecessary files like build output, `.git`, and IDE configs from the Docker build context to reduce image size and build time.
- Pin Go and base image versions in Dockerfiles for reproducibility.
- Disable CGO (`CGO_ENABLED=0`) when building to avoid linking to libc and enable static binaries.
- Prefer minimal base images like `scratch` or `alpine` for production runtimes. If `scratch` is too minimal, `alpine` provides a small Linux environment.
- Use multi-stage builds to separate compile-time dependencies from runtime.
- Cache Go module downloads between builds by copying `go.mod` and `go.sum` first.
- Use environment variables to configure your app in containers for portability across environments.
- For integration tests that involve databases or other services, Docker Compose networks can orchestrate dependencies.
Sample Full Dockerfile (Multi-Stage Build)
dockerfile
# Build stage
FROM golang:1.19 as builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# Production stage
FROM scratch
WORKDIR /app
COPY --from=builder /build/app .
EXPOSE 8080
CMD ["./app"]
Sample docker-compose.yml for Integration
yaml
version: '3.8'
services:
web:
build: .
ports:
- "808080"
depends_on:
- db
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=password
- DB_NAME=testdb
db:
image: postgres:13
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: testdb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Running Integration Tests with Docker
To run integration tests that require a database or external services:
1. Start dependencies with Docker Compose:
bash
docker-compose up -d db
2. Run tests in a container linked to the database service:
bash
docker build -t go-integration-test .
docker run --rm --network container: go-integration-test
Or run tests inside the app container with access to services by adding a test command override.
Summary
- Golang and Docker integration starts with containerizing the Go app using a Dockerfile.
- Multi-stage builds produce small, optimized images by separating build and runtime.
- Docker Compose manages multi-service architectures like Go app + database combinations.
- Running tests inside containers ensures consistent build and test environments.
- Live reloading in Docker speeds up development with source code sync and restart tooling.
- Best practices include version pinning, multi-stage builds, CGO disabling, and using `.dockerignore`.