Docker и Docker Compose для Go-проектов
Docker помогает запускать проект одинаково на разных машинах. Если на вашем ноутбуке Go одной версии, PostgreSQL другой, а у teammate вообще другая операционная система, Docker уменьшает количество "у меня не запускается".
Docker упаковывает приложение и его окружение в image. Контейнер - это запущенный image. Docker Compose связывает несколько контейнеров в одну локальную систему: backend, PostgreSQL, Redis, RabbitMQ, MinIO.
Зачем Docker Go-проекту
Go сам по себе компилируется в удобный бинарник. Но backend редко состоит только из одного бинарника. Ему нужны:
- база данных;
- переменные окружения;
- миграции;
- Redis;
- очереди;
- объектное хранилище;
- одинаковые команды запуска.
Docker полезен не потому, что Go без него не работает. Он полезен потому, что проект с зависимостями становится воспроизводимым.
Dockerfile для Go
Простой multi-stage Dockerfile:
FROM golang:1.22-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o server ./cmd/server FROM alpine:3.20 WORKDIR /app COPY /app/server ./server EXPOSE 8080 CMD ["./server"]
Первый этап собирает бинарник. Второй этап содержит только то, что нужно для запуска. Так итоговый образ меньше и не содержит весь набор инструментов Go.
Почему COPY go.mod отдельно
В Docker build есть cache. Если сначала скопировать go.mod и go.sum, а потом выполнить go mod download, Docker сможет переиспользовать слой с зависимостями, пока эти файлы не меняются.
COPY go.mod go.sum ./ RUN go mod download COPY . .
Если сразу сделать COPY . ., любое изменение исходного файла будет сбрасывать cache зависимостей.
Слушайте 0.0.0.0
Внутри контейнера backend должен слушать не только localhost, а 0.0.0.0:
http.ListenAndServe("0.0.0.0:8080", handler)
localhost внутри контейнера означает сам контейнер. Если сервис слушает только loopback, Docker port mapping может не дать доступ с хоста.
docker-compose.yml
Пример с API и PostgreSQL:
services: api: build: . ports: - "8080:8080" environment: DATABASE_URL: postgres://app:secret@postgres:5432/app?sslmode=disable depends_on: postgres: condition: service_healthy postgres: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: app ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d app"] interval: 5s timeout: 5s retries: 5
Внутри Compose сервисы обращаются друг к другу по имени: postgres, redis, rabbitmq.
Ports
Запись:
ports: - "8080:8080"
означает:
host_port:container_port
Левая часть доступна на вашей машине. Правая часть - порт внутри контейнера.
Для PostgreSQL:
ports: - "5432:5432"
Это позволяет подключиться к базе с хоста, например через psql или GUI-клиент.
Networks
Compose автоматически создает network для сервисов. Поэтому api может подключиться к postgres по имени сервиса:
postgres:5432
Не нужно узнавать IP контейнера. IP может меняться, имя сервиса остается стабильным.
Volumes
Чтобы данные PostgreSQL не исчезали после перезапуска:
volumes: postgres_data: services: postgres: volumes: - postgres_data:/var/lib/postgresql/data
Без volume контейнер можно удалить вместе с данными. Для базы это почти всегда неприятный сюрприз.
Healthcheck
depends_on без healthcheck ждет только запуска контейнера, а не готовности сервиса. PostgreSQL может быть запущен как процесс, но еще не готов принимать подключения.
healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d app"] interval: 5s timeout: 5s retries: 5
Healthcheck делает старт API более предсказуемым.
Переменные окружения
Для локальной разработки можно использовать .env, но не коммитить секреты:
services: api: env_file: - .env
В репозитории оставляйте .env.example:
DATABASE_URL=postgres://app:secret@postgres:5432/app?sslmode=disable PORT=8080
Так человек понимает, какие переменные нужны, но реальные секреты не попадают в Git.
Полезные команды
docker compose up --build docker compose up -d docker compose logs api docker compose logs -f api docker compose exec postgres psql -U app -d app docker compose down docker compose down -v
down -v удаляет volumes, то есть данные БД. Используйте осторожно.
Частые ошибки
- Использовать
localhostвместо имени сервиса внутри Compose. - Не добавить volume для базы.
- Считать, что
depends_onбез healthcheck ждет готовности PostgreSQL. - Коммитить
.envс секретами. - Слушать
127.0.0.1внутри контейнера. - Собирать image без
.dockerignore, отправляя в build context лишние файлы.
Чеклист
- Dockerfile использует multi-stage build.
go.modиgo.sumкопируются до исходников для cache.- Сервис слушает
0.0.0.0, а не толькоlocalhost. - Внутри Compose сервисы ходят друг к другу по имени.
- База имеет volume.
- У зависимостей есть healthcheck.
- Секреты не закоммичены.
- Есть
.env.example. - В README есть команды
docker compose upиdocker compose down.