Docker для Go (Golang): разработка, сборка и деплой

Курс о том, как контейнеризировать Go-приложения с помощью Docker: от базовых образов и сборки до оптимизации, окружения и развертывания. Рассматриваются практики multi-stage сборки, работа с зависимостями, конфигурацией и публикацией образов.

1. Основы Docker и подготовка окружения для Go

Основы Docker и подготовка окружения для Go

Docker — это инструмент, который упаковывает приложение вместе с зависимостями в контейнер, чтобы его было одинаково просто запускать на ноутбуке разработчика, на CI и на сервере. В курсе мы будем использовать Docker как стандартный способ сборки и деплоя Go-сервисов.

Что вы получите после этой статьи

  • Понимание базовых терминов Docker: образ, контейнер, Dockerfile, реестр образов.
  • Установленное окружение: Docker и Go.
  • Первый Go-сервис, запущенный в контейнере.
  • Базовые навыки работы с командами docker build, docker run, docker ps, docker logs.
  • Базовые понятия Docker простыми словами

    Образ (image) — это неизменяемый шаблон, из которого создаются контейнеры. В образе находится файловая система и метаданные: что запускать, какие переменные окружения, какие порты и т.д.

    Контейнер (container) — это запущенный (или остановленный) экземпляр образа. Контейнер можно создать, запустить, остановить и удалить.

    Dockerfile — текстовый файл с инструкциями, как собрать образ.

    Реестр образов (registry) — место, где хранятся готовые образы. Самый популярный — Docker Hub.

    !Как Dockerfile превращается в образ, а образ — в контейнеры, и где здесь реестр

    Полезные первоисточники:

  • Обзор Docker
  • Официальный образ Golang на Docker Hub
  • Установка Docker

    Варианты установки

  • Docker Desktop для Windows/macOS: удобен для локальной разработки.
  • Docker Engine для Linux: чаще используется на серверах и Linux-машинах.
  • Официальные инструкции:

  • Docker Desktop
  • Установка Docker Engine
  • Проверка установки Docker

    После установки проверьте версии:

    Проверьте, что демон Docker работает:

    И выполните тестовый контейнер:

    Если вы увидели сообщение о корректной установке, Docker готов.

    Установка Go

    Установите Go на хост-машину (это полезно для локальной разработки и проверки, даже если сборку мы будем делать в контейнере):

  • Установка Go
  • Проверка:

    Создаём минимальный Go-сервис

    Создайте папку проекта и файлы.

    Файл main.go

    Инициализация модуля

    В папке проекта выполните:

    Первый Dockerfile для Go

    Начнём с многостадийной сборки (multi-stage build): в первом этапе компилируем бинарник, во втором — запускаем его в более лёгком окружении. Это стандартная практика для Go.

    Создайте Dockerfile:

    Что здесь важно:

  • FROM golang:1.24 AS builder — первый этап, где есть Go toolchain.
  • WORKDIR /app — рабочая директория внутри образа.
  • COPY go.mod ./ и go mod download — скачиваем зависимости отдельно, чтобы Docker лучше кешировал слой.
  • CGO_ENABLED=0 — просим собрать статический бинарник (часто упрощает запуск в минимальных образах).
  • FROM alpine:3.21 — финальный минимальный образ для запуска.
  • EXPOSE 8080 — документация о том, какой порт слушает контейнер.
  • CMD — команда по умолчанию при запуске контейнера.
  • Ссылки на базовые образы:

  • Golang на Docker Hub
  • Alpine на Docker Hub
  • Сборка образа

    Соберите образ из текущей директории (точка в конце — контекст сборки):

    Проверьте, что образ появился локально:

    Запуск контейнера и проверка сервиса

    Запустите контейнер и пробросьте порт 8080 наружу:

    Проверка в другом терминале:

    Ожидаемый ответ:

    Базовые команды для повседневной работы

  • docker ps — список запущенных контейнеров.
  • docker ps -a — все контейнеры, включая остановленные.
  • docker logs <container> — логи контейнера.
  • docker exec -it <container> sh — зайти внутрь контейнера (если внутри есть sh).
  • docker stop <container> — остановить контейнер.
  • docker rm <container> — удалить контейнер.
  • docker rmi <image> — удалить образ.
  • Частые проблемы и быстрые решения

  • Контейнер сразу завершился
  • Проверьте логи: docker logs <container>
  • Порт занят на хосте
  • Измените левую часть проброса порта: -p 8081:8080
  • Изменения в коде не попадают в контейнер
  • Пересоберите образ: docker build -t hello-go:1 .
  • Куда мы идём дальше

    В этой статье вы подготовили окружение и собрали/запустили первый Go-сервис в Docker. Дальше по курсу мы будем улучшать Dockerfile для быстрых сборок, разберём работу с кэшем и зависимостями, научимся делать production-ready образы, а затем перейдём к запуску нескольких сервисов (обычно через Docker Compose) и к деплою.

    Справка на будущее:

  • Docker Compose
  • 2. Dockerfile для Go: сборка, зависимости и кэширование

    Dockerfile для Go: сборка, зависимости и кэширование

    В прошлой статье вы собрали и запустили минимальный Go-сервис в Docker и познакомились с многостадийной сборкой. Теперь сделаем Dockerfile быстрее, предсказуемее и удобнее для CI: разберём, как работают слои и кэш Docker, как правильно скачивать зависимости, как уменьшать контекст сборки, и как подключать кэш Go при сборке через BuildKit.

    Что вы получите после этой статьи

  • Понимание, почему один Dockerfile собирается за секунды, а другой — за минуты.
  • Умение управлять кэшированием слоёв через порядок инструкций.
  • Правильную схему работы с зависимостями Go: go.mod и go.sum отдельно от исходников.
  • Практику использования .dockerignore.
  • Production-ориентированный Dockerfile для Go с кэшами BuildKit.
  • Как работает кэш Docker при сборке

    Docker собирает образ по шагам из Dockerfile. Каждый шаг создаёт слой. Если Docker видит, что слой уже был собран ранее и входные данные не изменились, он берёт слой из кэша.

    Кэширование почти всегда ломается из-за двух причин:

  • Вы изменили команду в инструкции RUN.
  • Вы изменили файлы, которые попадают в инструкцию COPY (или изменился любой файл в контексте сборки, который влияет на этот COPY).
  • !Наглядно показывает, какие слои пересобираются при изменениях кода и зависимостей

    Главный принцип быстрого Dockerfile для Go

    Стабильные части раньше, изменяемые части позже.

    Для Go это означает:

  • Сначала копировать go.mod и go.sum.
  • Скачивать зависимости (go mod download).
  • Только потом копировать остальной исходный код и запускать go build.
  • Контекст сборки и .dockerignore

    Когда вы запускаете docker build ., точка означает контекст сборки: Docker отправляет на сборку файлы из текущей директории. Чем больше лишних файлов в контексте, тем:

  • дольше сборка (особенно в CI и на удалённом Docker daemon),
  • выше шанс “случайно” инвалидировать кэш,
  • больше риск утащить в образ секреты.
  • Чтобы исключить ненужные файлы, используйте .dockerignore.

    Официальная документация:

  • Docker build context и .dockerignore
  • Пример .dockerignore для Go

    Если вы используете vendor/, решите заранее стратегию:

  • либо исключить vendor/ и скачивать зависимости внутри сборки,
  • либо включить vendor/ и собирать с -mod=vendor.
  • Зависимости Go в Docker: go mod download как отдельный слой

    Go-модули описываются файлами:

  • go.mod — список зависимостей и минимальная версия Go,
  • go.sum — контрольные суммы, чтобы скачивание было воспроизводимым.
  • Официальная документация:

  • Go Modules Reference
  • Правильная схема в Dockerfile:

  • Скопировать go.mod и go.sum.
  • Выполнить go mod download.
  • Скопировать исходники.
  • Выполнить go build.
  • Тогда при изменении только .go файлов слой с go mod download останется закэшированным.

    Базовый production Dockerfile для Go

    Ниже — аккуратная версия многостадийной сборки, похожая на ту, что вы уже сделали, но с небольшими улучшениями.

    Что добавилось и зачем:

  • -trimpath — убирает локальные пути из бинарника, сборка становится более воспроизводимой.
  • ca-certificates — без них HTTPS-запросы из контейнера могут не работать.
  • USER app — запуск не от root, это базовая гигиена безопасности.
  • Ускорение сборки через BuildKit: кэш модулей и кэш компиляции

    Даже при правильном порядке слоёв есть проблема: кэш Docker работает на уровне слоёв, а Go при сборке использует свои кэши:

  • кэш модулей (скачанные зависимости),
  • кэш компиляции (результаты промежуточной компиляции).
  • Если вы хотите, чтобы повторные сборки были быстрыми даже при частых изменениях кода, удобно подключить кэш-тома сборки через BuildKit.

    Официальная документация:

  • Dockerfile reference (инструкция RUN)
  • Docker build cache и cache mounts
  • Dockerfile с cache mounts

    Как это работает:

  • --mount=type=cache,target=... создаёт кэшируемую директорию, которая сохраняется между сборками.
  • В target=/go/pkg/mod Go хранит скачанные модули.
  • В target=/root/.cache/go-build Go хранит кэш компиляции.
  • Включение BuildKit

    Во многих современных установках Docker BuildKit уже включён по умолчанию. Если у вас cache mounts не работают, попробуйте собрать так:

    Типичные ошибки, из-за которых кэш не работает

  • COPY . . выполняется до go mod download
  • - Тогда любое изменение в коде инвалидирует слой со скачиванием зависимостей.
  • Нет .dockerignore
  • - Например, изменился какой-то лог или файл IDE, Docker считает контекст изменённым.
  • go.mod меняется слишком часто
  • - Например, вы часто делаете go get или не фиксируете версии осознанно.
  • В Dockerfile используется установка зависимостей через go get ... вместо модулей
  • - Это делает сборку менее воспроизводимой.

    Практика: как проверять, что вы действительно используете кэш

  • Соберите образ два раза подряд и сравните вывод.
  • Для более детального вывода используйте:
  • Если нужно убедиться, что кэш не используется:
  • Что дальше по курсу

    Вы научились проектировать Dockerfile так, чтобы сборка Go-сервиса была быстрой и предсказуемой: минимальный контекст, правильная работа с зависимостями и кэшами.

    Дальше логичный шаг — запуск нескольких контейнеров вместе (обычно через Docker Compose), настройка переменных окружения, сетей и зависимостей между сервисами, а затем — подготовка образов и конфигураций к деплою.

    3. Multi-stage сборка и минимальные образы для продакшена

    Multi-stage сборка и минимальные образы для продакшена

    Multi-stage сборка — это приём, когда в одном Dockerfile вы используете несколько стадий: на одной собираете Go-бинарник, а на другой — запускаете его в максимально маленьком и безопасном окружении. В прошлых статьях вы уже делали многостадийную сборку и настраивали кэширование. Сейчас доведём подход до production-ready: научимся выбирать минимальный runtime-образ, учитывать CGO, сертификаты, пользователей, а также понимать компромиссы между размером, совместимостью и наблюдаемостью.

    Что вы получите после этой статьи

  • Понимание, зачем multi-stage — не только про размер, но и про безопасность.
  • Умение выбирать runtime-образ под задачу: alpine, scratch, distroless.
  • Готовые шаблоны Dockerfile для Go под продакшен.
  • Чек-лист того, что чаще всего ломается в минимальных образах.
  • !Диаграмма показывает, что сборка и запуск происходят в разных образах, а в runtime попадают только нужные файлы

    Почему multi-stage важно для продакшена

    Multi-stage решает сразу несколько практических проблем.

  • Размер образа
  • - В builder-стадии вам нужны компилятор, кэши, заголовки и утилиты. - В runtime-стадии чаще всего нужен только один файл: ваш бинарник.

  • Безопасность
  • - Меньше пакетов и утилит внутри runtime-образа означает меньше потенциальных уязвимостей и меньше возможностей для атакующего.

  • Предсказуемость и переносимость
  • - Вы контролируете, что именно попадает в финальный образ.

    Официальная документация по multi-stage:

  • Docker documentation: Use multi-stage builds
  • Что значит минимальный образ

    Минимальный образ — это runtime-образ, который содержит только то, что нужно приложению для работы.

    Обычно Go-сервису нужны:

  • сам бинарник;
  • сертификаты доверенных центров (ca-certificates), если сервис ходит по HTTPS;
  • иногда таймзона (tzdata), если вы форматируете время в локальной зоне;
  • непривилегированный пользователь;
  • корректно заданный порт и команда запуска.
  • Важно: чем меньше образ, тем выше шанс, что вам придётся явно добавить то, что в “толстых” образах было “по умолчанию”.

    Выбор runtime-образа: alpine, scratch, distroless

    Ниже — три популярных стратегии.

    | Runtime-стратегия | Размер | Отладка внутри контейнера | Совместимость | Когда выбирать | |---|---:|---|---|---| | alpine | небольшой | проще (есть sh, пакетный менеджер) | хорошая, но есть нюансы musl | большинство сервисов, когда важен баланс | | scratch | минимальный | почти невозможна | требуется статический бинарник и явные файлы (например, сертификаты) | максимально минимальный runtime, строгие требования | | distroless | небольшой | сложнее (как правило, без shell) | хорошая, есть варианты nonroot | продакшен, когда хотите минимальность без scratch-крайностей |

    Полезные источники:

  • Docker Hub: Alpine
  • Docker documentation: scratch
  • Distroless на GitHub
  • Нюанс Go: CGO_ENABLED и почему он влияет на образ

    В Dockerfile для Go часто встречается CGO_ENABLED=0. Это просьба собрать бинарник без привязки к системным C-библиотекам.

    Практический смысл:

  • CGO_ENABLED=0 чаще позволяет получить бинарник, который проще запускать в минимальных образах, включая scratch.
  • CGO_ENABLED=1 может быть нужен, если у вас зависимости, которые используют cgo (например, некоторые драйверы, интеграции, специфичные библиотеки).
  • Если вы собираете статический бинарник, то runtime-образ может быть почти пустым. Если бинарник динамически связан, вам придётся тащить нужные библиотеки в runtime, и минимальность становится сложнее.

    Официальная документация:

  • Go documentation: cgo
  • Production Dockerfile: builder + alpine runtime

    Это самый практичный “дефолт” для многих Go HTTP-сервисов.

    Что здесь важно для продакшена:

  • -ldflags="-s -w" уменьшает бинарник, убирая часть отладочной информации.
  • ca-certificates почти всегда нужен, если сервис делает HTTPS-запросы.
  • USER app — базовая мера безопасности: приложение не работает от root.
  • Максимальная минимальность: scratch runtime

    scratch — это пустой образ. В нём нет shell, нет пакетного менеджера, нет сертификатов. Поэтому вы должны скопировать всё нужное сами.

    Ниже — практичный шаблон: делаем отдельную стадию для сертификатов на базе alpine, а затем копируем их в scratch.

    Практические ограничения scratch:

  • если ваше приложение пишет файлы, вам нужно продумать, куда и с какими правами;
  • отладка через docker exec почти бесполезна, потому что внутри нет sh;
  • нужно строго следить, чтобы бинарник был совместим (часто это означает CGO_ENABLED=0).
  • Production-вариант без shell: distroless

    Distroless-образы обычно содержат только минимальный набор runtime-зависимостей (например, сертификаты), но не содержат shell. Это снижает “шум” и поверхность атаки, но при этом обычно проще, чем scratch.

    Пример для статически собранного Go-бинарника и запуска от непривилегированного пользователя:

    Что стоит учесть:

  • внутри, как правило, нет shell, поэтому диагностика делается через логи, метрики и трассировку;
  • используйте вариант :nonroot, чтобы сразу запускаться не от root.
  • Типичные проблемы минимальных образов и как их диагностировать

  • HTTPS-запросы не работают, ошибки про сертификаты
  • 1. Проверьте, что в runtime есть ca-certificates или скопирован файл ca-certificates.crt. 2. Убедитесь, что приложение действительно использует системное хранилище сертификатов.

  • Контейнер стартует и сразу падает
  • 1. Посмотрите логи: docker logs <container>. 2. Проверьте, что путь в CMD совпадает с тем, куда вы скопировали бинарник.

  • Не удаётся “зайти” внутрь контейнера
  • 1. Это ожидаемо для scratch и многих distroless. 2. Для диагностики используйте логи, а для временной отладки делайте отдельный debug-образ на базе alpine.

  • Приложению нужен доступ к файлам, но прав нет
  • 1. Если вы используете USER app, проверьте права на рабочую директорию. 2. Если нужны временные файлы, продумайте каталог и права заранее.

    Практический чек-лист production Dockerfile для Go

  • Multi-stage: сборка отделена от запуска.
  • Повторяемость: зависимости ставятся через go.mod и go.sum отдельным слоем.
  • Минимальный runtime: копируете только то, что нужно для запуска.
  • Сертификаты: добавлены ca-certificates (или скопированы в scratch).
  • Не root: USER задан явно или используется runtime-образ nonroot.
  • Размер бинарника: используете -trimpath и, при необходимости, -ldflags="-s -w".
  • Что дальше по курсу

    Вы научились собирать Go-сервис так, чтобы финальный образ был минимальным и пригодным для продакшена. Следующий логичный шаг — запуск нескольких контейнеров вместе (обычно через Docker Compose), передача конфигурации через переменные окружения, сети, зависимости между сервисами и подготовка к деплою.

    Полезная документация на будущее:

  • Docker documentation: Docker Compose overview
  • 4. Docker Compose для локальной разработки и сервисов

    Docker Compose для локальной разработки и сервисов

    В прошлых статьях вы научились собирать Go-сервис через Dockerfile, ускорять сборку кэшем и делать production-ready минимальные образы. Следующий практический шаг — запускать несколько контейнеров вместе: ваш Go-сервис, база данных, кэш, миграции, локальные утилиты (например, админка БД). Для этого используют Docker Compose.

    Docker Compose — это инструмент, который описывает набор контейнеров (сервисы), сети и хранилища (volumes) в одном файле compose.yml, а затем поднимает всё одной командой.

    Официальные источники:

  • Docker Compose overview
  • Compose file reference
  • Что вы получите после этой статьи

  • Понимание, как устроен compose.yml и что такое service, network, volume.
  • Умение запускать Go-сервис вместе с PostgreSQL одной командой.
  • Понимание, как прокидывать конфигурацию через переменные окружения.
  • Умение подключать volume для данных БД и bind mount для разработки.
  • Набор команд для повседневной работы: docker compose up, down, logs, exec.
  • !Как сервисы связываются в Compose через сеть, порты и volume

    Базовые понятия Compose

  • Service — описание контейнера: образ, сборка, переменные, порты, тома, команда запуска.
  • Network — виртуальная сеть, где сервисы видят друг друга по DNS-именам.
  • Volume — постоянное хранилище данных, переживающее пересоздание контейнеров.
  • Ключевой принцип:

  • На хосте вы подключаетесь к сервисам через проброшенные порты.
  • Внутри Compose-сети сервисы общаются по имени сервиса, например postgres:5432, а не localhost.
  • Минимальный compose.yml: поднимаем Go-сервис

    Если у вас уже есть Dockerfile из прошлых статей, Compose может просто собрать и запустить контейнер.

    Запуск:

    Остановка и удаление контейнеров, созданных Compose:

    Практический пример: Go + PostgreSQL для локальной разработки

    Предположим, ваш Go-сервис слушает :8080 и подключается к PostgreSQL по DSN из переменной окружения DATABASE_URL.

    Compose-файл с базой, volume и переменными окружения

    Что здесь происходит:

  • postgres берётся из готового образа postgres:16.
  • Данные БД сохраняются в pgdata, поэтому они не пропадут после docker compose down и повторного up.
  • app собирается из текущего проекта (build.context: .) и получает DATABASE_URL.
  • В DATABASE_URL хост — это postgres, потому что так называется сервис, и Compose добавляет DNS-запись внутри сети.
  • Важный нюанс про depends_on

    depends_on гарантирует порядок старта контейнеров, но не гарантирует, что PostgreSQL уже готов принимать соединения. Если приложение подключается к БД на старте, добавьте повторные попытки подключения в коде или используйте healthcheck.

    Пример healthcheck для PostgreSQL и ожидание по состоянию здоровья:

    Условия depends_on.condition поддерживаются в актуальной спецификации Compose, но поведение может отличаться в зависимости от версии Compose. Всегда проверяйте на своей версии и не полагайтесь только на Compose как на единственный механизм ожидания.

    Переменные окружения и файлы конфигурации

    Есть три популярных способа прокинуть конфигурацию.

  • environment прямо в compose.yml
  • env_file (например, .env.local)
  • файл .env в папке проекта для подстановки переменных в compose.yml
  • Пример с env_file:

    А .env.local может содержать:

    Рекомендации:

  • Не коммитьте реальные секреты (пароли, токены) в репозиторий.
  • Для локальной разработки допустимы простые пароли, но лучше привыкать к аккуратной конфигурации.
  • Volumes и bind mounts: данные против исходников

    Volume — лучший вариант для данных сервисов (PostgreSQL, Redis), потому что Docker управляет жизненным циклом и правами.

    Bind mount — полезен в разработке, когда вы хотите, чтобы контейнер видел ваши локальные исходники.

    Пример bind mount для app:

    Важно понимать ограничение: если ваш образ собирает бинарник и запускает его, то просто примонтировать исходники недостаточно — нужно либо запускать go run, либо использовать инструмент hot-reload, либо пересобирать образ.

    Отдельная конфигурация для разработки: Dockerfile.dev

    Практичный подход: иметь отдельный Dockerfile для разработки, где есть Go toolchain и команда запуска ориентирована на быстрые итерации.

    Пример Dockerfile.dev:

    Compose для разработки:

    Компромисс этого режима:

  • Запуск будет медленнее, чем у готового бинарника.
  • Зато вы можете быстро менять код без docker build на каждый чих.
  • Полезные команды Docker Compose для ежедневной работы

  • docker compose up --build — собрать и запустить.
  • docker compose up -d — запустить в фоне.
  • docker compose down — остановить и удалить контейнеры сети.
  • docker compose down -v — дополнительно удалить volumes (например, чтобы сбросить БД).
  • docker compose logs -f app — смотреть логи конкретного сервиса.
  • docker compose exec app sh — зайти внутрь контейнера (если там есть sh).
  • docker compose ps — список сервисов и их статус.
  • Документация по командам:

  • docker compose command reference
  • Типичные ошибки при работе с Compose

  • Использование localhost для обращения к БД из контейнера
  • - Правильно: postgres:5432 (по имени сервиса). - localhost внутри контейнера — это сам контейнер.

  • Потеря данных БД при пересоздании
  • - Решение: использовать volumes для каталога данных БД.

  • depends_on не спасает от “БД ещё не готова”
  • - Решение: retry в приложении и/или healthcheck.

  • Слишком большой контекст сборки и медленный docker compose build
  • - Решение: .dockerignore и правильный порядок слоёв в Dockerfile из прошлой статьи.

    Как это связывается с продакшеном

    Compose в первую очередь удобен для локальной разработки и CI-окружений, но принципы переносимы:

  • ваш runtime-образ остаётся таким же минимальным, как в статье про multi-stage;
  • конфигурация передаётся через переменные окружения;
  • сервисы разделены (app отдельно от БД);
  • данные вынесены в постоянное хранилище.
  • Дальше по курсу обычно переходят к тому, как собирать и публиковать образы в реестр, как настраивать CI для сборки, и как деплоить (в том числе в окружения, где Compose уже не является основным инструментом).

    5. Деплой и эксплуатация: registry, теги, безопасность и логи

    Деплой и эксплуатация: registry, теги, безопасность и логи

    После того как вы научились писать быстрые Dockerfile, делать минимальные production-образы и поднимать сервисы через Docker Compose, следующий шаг в реальной работе — доставить образ в окружение (сервер, кластер, staging) и эксплуатировать его: версионировать, обновлять, ограничивать риски, наблюдать поведение через логи.

    В этой статье разберём четыре практические темы:

  • Registry: где хранить образы и как их публиковать
  • Теги: как версионировать образы так, чтобы деплой был предсказуемым
  • Безопасность: базовая гигиена контейнеров и образов
  • Логи: как устроено логирование контейнеров и как его не сломать
  • !Общая картина пути от кода до запущенного контейнера

    Registry: где живут ваши Docker-образы

    Registry — это сервис, который хранит Docker-образы и отдаёт их по запросу. Самые частые варианты:

  • Docker Hub
  • GitHub Container Registry (GHCR)
  • Частные registry (например, Harbor) и облачные (например, ECR/GCR/ACR)
  • С точки зрения Docker-клиента, работа везде похожа: логин, тегирование, push/pull.

    Полезные источники:

  • Docker docs: Image registry
  • Docker Hub
  • GitHub docs: GitHub Container Registry
  • Имена образов и репозиториев

    Образ обычно выглядит так:

  • registry/namespace/repo:tag
  • Примеры:

  • docker.io/myuser/hello-go:1.2.3
  • ghcr.io/myorg/hello-go:1.2.3
  • Если registry не указан, Docker по умолчанию использует Docker Hub.

    Логин в registry

    Docker Hub:

    GHCR:

    Практика для CI:

  • вместо пароля используйте token с минимальными правами
  • не печатайте секреты в логах
  • Теги образов: как версионировать, чтобы деплой был управляемым

    Тег — это человеко-читаемая метка на конкретный образ. Ключевая мысль: тег не обязан быть неизменяемым, но для эксплуатации лучше считать, что в продакшене теги должны быть предсказуемыми.

    Рекомендуемая стратегия тегов

    Ниже — практичная схема, которая хорошо работает для Go-сервисов.

    | Тег | Пример | Для чего | Риск | |---|---|---|---| | SemVer релиз | 1.4.2 | продакшен-релизы | низкий | | Git SHA | sha-3f2c1d7 | точная привязка к коду | низкий | | Ветка | main | превью и тестовые окружения | средний | | latest | latest | демо и локальные эксперименты | высокий |

    Практический вывод:

  • для деплоя используйте 1.4.2 и или sha-...
  • избегайте деплоя по latest, потому что он меняется и ломает воспроизводимость
  • !Показывает, почему тег может “переехать”, а digest — нет

    Digest: неизменяемая ссылка на образ

    Docker умеет работать не только с тегами, но и с digest вида @sha256:.... Digest однозначно идентифицирует содержимое образа.

    Пример:

    Практика:

  • теги удобны человеку
  • digest удобен для максимальной воспроизводимости
  • Официально:

  • Docker docs: Image digests
  • Публикация образа: build, tag, push

    Предположим, у вас уже есть production Dockerfile с multi-stage, как в прошлых статьях.

    Локальная публикация в Docker Hub

    1) Собрать образ:

    2) Присвоить “удалённое” имя (тегирование):

    3) Запушить:

    Полезно:

  • Docker docs: docker tag
  • Docker docs: docker push
  • Multi-arch образы (часто важно для серверов и Apple Silicon)

    Если вам нужно поддерживать разные архитектуры (например, linux/amd64 и linux/arm64), используйте buildx.

    Пример сборки и пуша multi-arch образа:

    Официально:

  • Docker docs: docker buildx
  • Деплой на сервере: базовый сценарий без оркестратора

    Если у вас простой сервер и вы деплоите контейнер напрямую через Docker Engine, типичный цикл такой:

    1) Залогиниться в registry на сервере (или заранее настроить доступ) 2) Скачать новую версию образа 3) Перезапустить контейнер

    Пример:

    Что важно:

  • --restart=always помогает переживать перезапуски демона Docker и ребуты
  • внешний порт выбирайте так, чтобы он не конфликтовал с другими сервисами
  • Документация:

  • Docker docs: Restart policies
  • Деплой через Compose на сервере

    Если вы уже описывали сервисы через Compose (приложение, база, миграции), деплой часто делают так:

    Официально:

  • Docker docs: docker compose up
  • Безопасность эксплуатации: минимальный набор правил

    Контейнеры упрощают изоляцию, но не делают приложение автоматически безопасным. Ниже — практичный минимум.

    Запуск не от root

    В прошлых статьях вы уже добавляли USER app или использовали distroless:nonroot. Это снижает ущерб, если процесс будет скомпрометирован.

    Проверка внутри запущенного контейнера (если есть sh):

    Минимизируйте runtime-образ

    Подход из multi-stage даёт сразу два выигрыша:

  • меньше поверхность атаки
  • меньше уязвимых пакетов
  • Официально:

  • Docker docs: Multi-stage builds
  • Не храните секреты в образе

    Запрещённые практики:

  • COPY .env ./ в образ
  • ARG DB_PASSWORD=... и использование в Dockerfile так, что секрет попадёт в слои
  • Нормальные варианты:

  • передавать секреты как переменные окружения на этапе запуска
  • использовать секреты платформы (например, secret storage вашего CI и окружения)
  • Для Compose в продакшене секреты обычно решают средствами окружения, а не коммитом в репозиторий.

    Документация:

  • Docker docs: Build secrets
  • Ограничьте возможности контейнера на запуске

    Если вы запускаете контейнер напрямую, добавляйте ограничения, когда это возможно:

  • файловая система только на чтение: --read-only
  • запрет повышения привилегий: --security-opt no-new-privileges:true
  • лимиты ресурсов: --memory, --cpus
  • Пример (подходит не всем приложениям, потому что многим нужен writable каталог для временных файлов):

    Документация:

  • Docker docs: docker run
  • Сканирование уязвимостей образов

    Практика:

  • сканируйте образы в CI до публикации
  • периодически пересобирайте образы, чтобы получить обновления базового образа
  • Инструменты:

  • Docker Scout (в экосистеме Docker)
  • Trivy (популярный open-source)
  • Ссылки:

  • Docker docs: Docker Scout
  • Trivy на GitHub
  • Логи: как устроено логирование контейнеров

    Главное правило контейнерного логирования:

  • пишите логи в stdout и stderr, а не в файлы внутри контейнера
  • Почему:

  • Docker умеет собирать stdout/stderr стандартными средствами
  • контейнеры часто пересоздаются, и файлы внутри них не считаются надёжным хранилищем
  • Просмотр логов

    Один контейнер:

    Compose-сервис:

    Документация:

  • Docker docs: docker logs
  • Docker docs: docker compose logs
  • Драйверы логирования

    Docker поддерживает разные log drivers. По умолчанию часто используется json-file. В продакшене нередко настраивают отправку в централизованную систему.

    Примеры драйверов:

  • json-file (локально на хосте)
  • journald (на системах с systemd)
  • fluentd, syslog (для централизованного сбора)
  • Документация:

  • Docker docs: Configure logging drivers
  • Структурированные логи в Go

    Для эксплуатации удобнее, когда логи структурированы (например, JSON) и содержат поля:

  • уровень (info, error)
  • сообщение
  • время
  • request id или trace id
  • контекст (например, user_id, order_id)
  • Практический минимум:

  • логируйте ключевые события старта: порт, версия, commit SHA
  • логируйте ошибки с контекстом
  • не логируйте секреты
  • Добавьте версию образа в логи

    Удобный приём: передать в контейнер переменную APP_VERSION и печатать её на старте.

    Запуск:

    Это помогает быстро понять, что именно сейчас крутится в окружении, особенно когда теги веток двигаются.

    Практический чек-лист перед деплоем

  • Образ собран multi-stage и в runtime нет лишнего
  • Тег выбран предсказуемый: релизный 1.0.0 и или sha-...
  • Секреты не попали в образ и не закоммичены
  • Запуск не от root
  • Логи идут в stdout/stderr
  • Рестарт-политика задана, чтобы сервис поднимался после сбоя
  • Сканирование образа встроено в CI или выполняется регулярно
  • Куда двигаться дальше

    На этом курсе вы получили полный цикл:

  • Dockerfile для Go с кэшированием и multi-stage
  • минимальные runtime-образы
  • Compose для локальной разработки и связки сервисов
  • публикация образов в registry и базовая эксплуатация
  • Дальше обычно углубляются в CI/CD (автосборка и автопубликация образов), инфраструктурный деплой (Kubernetes или managed PaaS), метрики и трассировку, а также управление секретами на уровне платформы.