Docker и Go: контейнеризация приложений на Golang с нуля

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Docker и Go: полное руководство по контейнеризации Golang-приложений

Контейнеризация стала стандартом де-факто для развертывания современных приложений. Язык Go (Golang) идеально подходит для контейнерной среды: его скомпилированные бинарные файлы не требуют рантайма, а статическая линковка позволяет создавать минимальные образы Docker. В этой статье мы разберем процесс контейнеризации Go-приложений от написания Dockerfile до оптимизации размера итогового образа.

Почему Go и Docker — идеальная пара

Go компилируется в один статический бинарный файл, который не зависит от системных библиотек. Это означает, что для запуска Go-приложения в контейнере не нужно устанавливать Go Runtime, интерпретаторы или дополнительные зависимости. В результате вы получаете:

  • Минимальный размер образа — часто менее 10-15 МБ
  • Быстрый запуск — миллисекунды, а не секунды
  • Высокую безопасность — меньше компонентов = меньше уязвимостей
  • Простоту деплоя — один бинарный файл вместо множества скриптов и библиотек

Рассмотрим это на примере простого HTTP-сервера на Go.

Шаг 1: Создаем Go-приложение

Для начала напишем простое веб-приложение, которое будем контейнеризировать. Создайте файл main.go:

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Привет из контейнера Go! 🐳")
    })

    log.Printf("Сервер запущен на порту %s", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

Это минимальный HTTP-сервер, который слушает порт, заданный переменной окружения PORT (по умолчанию 8080).

Шаг 2: Многоэтапная сборка (Multi-stage build)

Для создания минимального образа Docker используется многоэтапная сборка. Первый этап — компиляция приложения, второй — создание финального образа только с бинарным файлом. Создайте Dockerfile:

# Этап 1: Сборка приложения
FROM golang:1.22-alpine AS builder

WORKDIR /app

# Копируем go.mod и go.sum для кэширования зависимостей
COPY go.mod go.sum ./
RUN go mod download

# Копируем исходный код
COPY . .

# Собираем статический бинарный файл
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .

# Этап 2: Финальный минимальный образ
FROM alpine:3.19

# Добавляем CA-сертификаты для HTTPS
RUN apk --no-cache add ca-certificates

WORKDIR /root/

# Копируем бинарник из первого этапа
COPY --from=builder /app/server .

# Открываем порт
EXPOSE 8080

# Запускаем приложение
CMD ["./server"]

Обратите внимание на ключевые моменты:

  • golang:1.22-alpine — легковесный образ для сборки
  • CGO_ENABLED=0 — отключаем CGO для статической линковки
  • alpine:3.19 — минимальный Linux-образ (~5 МБ)
  • ca-certificates — нужны, если приложение использует HTTPS

Шаг 3: Сборка и запуск контейнера

Соберите Docker-образ и запустите контейнер:

# Сборка образа
docker build -t go-hello:latest .

# Запуск контейнера
docker run -d -p 8080:8080 --name go-app go-hello:latest

# Проверка
curl http://localhost:8080
# Вывод: Привет из контейнера Go! 🐳

Проверьте размер полученного образа:

docker images go-hello
# REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
# go-hello     latest    abc123def456   10 seconds ago   14.2 MB

Всего 14.2 МБ — это ничтожно мало по сравнению с образами на Node.js (сотни МБ) или Python (десятки МБ).

Шаг 4: Оптимизация Dockerfile для продакшена

Для серьезных проектов стоит добавить несколько улучшений. Вот продвинутый Dockerfile:

# Этап 1: Сборка
FROM golang:1.22-alpine AS builder

# Устанавливаем зависимости для сборки
RUN apk add --no-cache git

WORKDIR /app

# Кэшируем зависимости
COPY go.mod go.sum ./
RUN go mod download

COPY . .

# Сборка с оптимизациями
RUN CGO_ENABLED=0 GOOS=linux go build \
    -ldflags="-w -s" \
    -o /app/server .

# Этап 2: Минимальный образ
FROM scratch

# Копируем только бинарник (без Alpine)
COPY --from=builder /app/server /server

EXPOSE 8080

CMD ["/server"]

Что изменилось:

  • -ldflags="-w -s" — убирает отладочную информацию, уменьшая размер бинарника
  • FROM scratch — абсолютно пустой образ (0 МБ), в котором есть только наш бинарник
  • Отсутствие Alpine — если приложение не использует системные вызовы, Shell не нужен

После такой сборки размер образа может составлять менее 7 МБ.

Шаг 5: Docker Compose для Go-приложения

Если ваше приложение использует базу данных или другие сервисы, удобно использовать Docker Compose. Создайте docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - DB_HOST=postgres
      - DB_PORT=5432
    depends_on:
      - postgres

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Запускается всё одной командой:

<

Блоги

Рекомендаций по книгам