Node.js образ: ~150MB (slim) + node_modules → Go образ: ~8MB (distroless!)
npm run build перед Docker → Multi-stage build в одном Dockerfile
docker-compose.yml → тот же формат, но Go-сервисы стартуют мгновенно
.dockerignore → как .gitignore, исключает бинарники и кэш
Go не нужен рантайм — бинарник работает сам!
mkdir go-docker && cd go-docker
go mod init go-docker
# Создаём простое HTTP-приложение
mkdir -p cmd/server
cmd/server/main.gopackage main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"
)
// AppInfo — информация о приложении
type AppInfo struct {
Service string `json:"service"`
Version string `json:"version"`
Hostname string `json:"hostname"`
Time string `json:"time"`
}
var version = "dev" // Заполняется при сборке через -ldflags
func main() {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
hostname, _ := os.Hostname()
mux := http.NewServeMux()
// Health check
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
// Info
mux.HandleFunc("GET /info", func(w http.ResponseWriter, r *http.Request) {
info := AppInfo{
Service: "go-docker-demo",
Version: version,
Hostname: hostname,
Time: time.Now().Format(time.RFC3339),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(info)
})
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("🚀 Сервер запущен на :%s (версия: %s)", port, version)
if err := http.ListenAndServe(":"+port, mux); err != nil {
log.Fatalf("Ошибка сервера: %v", err)
}
}
Dockerfile (Multi-stage)# ╔══════════════════════════════════════════════════════════╗
# ║ ЭТАП 1: СБОРКА (builder) ║
# ║ Используем полный образ Go с компилятором ║
# ╚══════════════════════════════════════════════════════════╝
FROM golang:1.22-alpine AS builder
# Устанавливаем git (нужен для go mod)
RUN apk add --no-cache git ca-certificates
# Создаём рабочую директорию
WORKDIR /app
# Копируем ТОЛЬКО файлы зависимостей (кэширование слоёв!)
COPY go.mod go.sum ./
RUN go mod download && go mod verify
# Копируем исходный код
COPY . .
# Собираем бинарник
# CGO_ENABLED=0 — отключаем CGO (чистый Go, без libc)
# -ldflags="-w -s" — убираем отладочную информацию (уменьшаем размер)
# -ldflags="-X main.version=..." — встраиваем версию
ARG VERSION=dev
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build \
-ldflags="-w -s -X main.version=${VERSION}" \
-o /app/server \
./cmd/server
# ╔══════════════════════════════════════════════════════════╗
# ║ ЭТАП 2: ФИНАЛЬНЫЙ ОБРАЗ (production) ║
# ║ Варианты (раскомментируйте нужный): ║
# ╚══════════════════════════════════════════════════════════╝
# --- ВАРИАНТ А: Distroless (рекомендовано, ~8MB) ---
FROM gcr.io/distroless/static-debian12:nonroot AS production
# nonroot — запускается от пользователя 65532 (не root!)
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Healthcheck (curl в distroless нет, используем скрипт или Kubernetes probes)
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD ["/server"] || exit 1
EXPOSE 8080
USER nonroot
ENTRYPOINT ["/server"]
# --- ВАРИАНТ Б: Scratch (минимальный, ~3MB бинарник + образ) ---
# FROM scratch
# COPY --from=builder /app/server /server
# COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# EXPOSE 8080
# ENTRYPOINT ["/server"]
# --- ВАРИАНТ В: Alpine (если нужны shell-утилиты, ~12MB) ---
# FROM alpine:3.19
# RUN apk --no-cache add ca-certificates tzdata curl
# COPY --from=builder /app/server /server
# HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
# CMD curl -f http://localhost:8080/health || exit 1
# EXPOSE 8080
# ENTRYPOINT ["/server"]
# --- ВАРИАНТ Г: Для разработки (Hot Reload через Air) ---
# FROM golang:1.22-alpine AS development
# RUN apk add --no-cache git
# RUN go install github.com/cosmtrek/air@latest
# WORKDIR /app
# COPY go.mod go.sum ./
# RUN go mod download
# CMD ["air", "-c", ".air.toml"]
docker-compose.ymlversion: '3.9'
services:
# ╔══════════════════════════════════════════════════════╗
# ║ GO-СЕРВИС ║
# ╚══════════════════════════════════════════════════════╝
app:
build:
context: .
dockerfile: Dockerfile
args:
VERSION: "1.0.0"
image: go-docker-app:latest
container_name: go-app
ports:
- "8080:8080"
environment:
- PORT=8080
- DATABASE_URL=postgres://postgres:secret@postgres:5432/appdb?sslmode=disable
- REDIS_URL=redis://redis:6379/0
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
# Resource limits
deploy:
resources:
limits:
cpus: '0.5'
memory: '128M'
reservations:
cpus: '0.1'
memory: '64M'
# ╔══════════════════════════════════════════════════════╗
# ║ POSTGRESQL ║
# ╚══════════════════════════════════════════════════════╝
postgres:
image: postgres:16-alpine
container_name: go-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./migrations:/docker-entrypoint-initdb.d # Авто-миграции при первом запуске
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
deploy:
resources:
limits:
memory: '256M'
# ╔══════════════════════════════════════════════════════╗
# ║ REDIS ║
# ╚══════════════════════════════════════════════════════╝
redis:
image: redis:7-alpine
container_name: go-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped
deploy:
resources:
limits:
memory: '64M'
# ╔══════════════════════════════════════════════════════╗
# ║ PROMETHEUS (опционально) ║
# ╚══════════════════════════════════════════════════════╝
prometheus:
image: prom/prometheus:latest
container_name: go-prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
restart: unless-stopped
volumes:
postgres_data:
redis_data:
prometheus_data:
.dockerignore# Бинарники
*.exe
*.exe~
*.dll
*.so
*.dylib
/server
/client
# Директории
.git/
.github/
.vscode/
.idea/
vendor/
tmp/
data/
# Файлы
*.test
*.out
*.prof
.env.local
.env.production
docker-compose.override.yml
README.md
Makefile
# Кэш Go
.cache/
prometheus.ymlglobal:
scrape_interval: 15s
scrape_configs:
- job_name: 'go-app'
static_configs:
- targets: ['app:8080']
Makefile# ╔══════════════════════════════════════════════════════════╗
# ║ MAKEFILE — удобные команды для Docker ║
# ╚══════════════════════════════════════════════════════════╝
.PHONY: help build run stop clean logs lint test
help: ## Показать справку
@grep -E '^[a-zA-Z_-]+:.*?## .*$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $1, $2}'
build: ## Собрать Docker-образ
docker build --build-arg VERSION=$(git describe --tags --always) -t go-docker-app:latest .
run: ## Запустить все сервисы
docker-compose up -d
stop: ## Остановить все сервисы
docker-compose down
restart: stop run ## Перезапустить все сервисы
logs: ## Показать логи
docker-compose logs -f app
shell: ## Зайти в контейнер (если alpine)
docker-compose exec app sh
clean: ## Очистить всё
docker-compose down -v
docker rmi go-docker-app:latest 2>/dev/null || true
lint: ## Линтер
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run
test: ## Тесты
go test -v -race -cover ./...
test-docker: ## Тесты в Docker
docker-compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from tests
# Сборка Docker-образа
docker build -t go-docker-app:latest .
# Сборка с версией
docker build --build-arg VERSION=1.0.0 -t go-docker-app:1.0.0 .
# Размер образа (distroless)
docker images go-docker-app
# Запуск через docker-compose
docker-compose up -d
# Проверка
curl http://localhost:8080/health
curl http://localhost:8080/info
# Логи
docker-compose logs -f app
# Остановка
docker-compose down
# Остановка с удалением томов
docker-compose down -v
| Базовый образ | Размер бинарника | Размер образа | Безопасность |
|---|---|---|---|
| Scratch | ~3 MB | ~3 MB | Максимальная (нет ничего лишнего) |
| Distroless | ~3 MB | ~8 MB | Высокая (ca-certificates, nonroot) |
| Alpine | ~3 MB | ~12 MB | Средняя (есть shell, пакеты) |
| Ubuntu/Debian | ~3 MB | ~100 MB | Ниже (много пакетов) |
| Node.js (сравнение) | — | ~150 MB (slim) | — |
USER nonroot.💡 Best practices от сеньоров:
💡 Для Node.js разработчика: