Files
go-synapse-backupper/.agents/skills/chore/dockerfile-skill/SKILL.md
T

290 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: dockerfile-skill
description: Ты ОБЯЗАН использовать этот скилл, если хочешь писать Dockerfile
---
## Multistage
Используй Docker multistage, если тебе надо создать Dockerfile для сборки и запуска приложения – раздели его на стейдж со сборкой и запуском.
Не стесняйся использовать `COPY FROM`.
Объединяй несколько команд `RUN` в одну строку с помощью `&&` и очищай временные файлы внутри того же слоя (например, `rm -rf /var/lib/apt/lists/*`). Это уменьшает число слоёв и размер финального образа.
Для зависимостей, которые часто пересобираются (pip, npm, apt), используй `--mount=type=cache,target=/root/.cache/pip` (или аналогично) – это ускоряет повторные сборки за счёт кэширования на хосте.
## Parallel
Разделяй стейджи Dockerfile таким образом, чтобы они могли выполняться параллельно (например, сборка зависимостей и подготовка тестовых данных).
При использовании BuildKit можно объявить несколько независимых `FROM` и копировать между ними через `COPY --from=...`. Убедись, что стейджи не имеют неявных зависимостей друг от друга.
## Makefile
Если в проекте есть Makefile, постарайся использовать его.
Если Makefile нет, сделай его и вызывай команды через таргеты, а не напрямую в Dockerfile.
Также добавь в этот Makefile таргеты для сборки и запуска текущего приложения в Docker.
## Compose
Если его еще нет, сделай возможность запустить приложение, для которого пишешь Dockerfile, в Docker Compose.
## Security
Используй лучшие практики безопасности при написании Dockerfile.
* **Фиксируй версии базовых образов** – всегда используй конкретный тег (например, `python:3.11-slim-bookworm`) вместо `:latest` или плавающих тегов, чтобы обеспечить воспроизводимость сборки.
* **Создавай выделенного непривилегированного пользователя** внутри образа: `RUN addgroup -S app && adduser -S -G app app`. Затем переключайся на него с помощью `USER app`. Копируй файлы командой `COPY --chown=app:app`, чтобы они сразу принадлежали этому пользователю, избегая лишних операций с правами.
* **Не храни секреты в переменных окружения образа** – используй `ARG` только для нечувствительных данных (например, версий пакетов), а секреты передавай через `--build-arg` в момент сборки или монтируй через Docker Secrets в рантайме.
* **Запускай контейнер rootless**, если это возможно. После создания пользователя и переключения на него финальный процесс будет работать без прав root.
* **Добавь `HEALTHCHECK`** (например, `HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/ || exit 1`), чтобы оркестраторы могли корректно определять состояние приложения.
* **Проставляй метаданные через `LABEL`** например, `LABEL maintainer="team@example.com"`, `LABEL version="1.0.0"`, `LABEL description="..."`. Это стандарт для документирования образов.
* **Указывай в Dockerfile флаги уменьшения размера зависимостей** – для `apt` используй `--no-install-recommends`, для `pip` `--no-cache-dir`, для `npm` `--only=production`, для `composer` `--no-dev`.
* **Сканируй собранный образ на уязвимости** – в CI/CD используй инструменты вроде `trivy`, `docker scout` или `grype`. Это должно быть частью пайплайна сборки.
## Dockerignore
*Создай файл `.dockerignore` в корне проекта. Включи в него все ненужные файлы и директории: `.git`, `node_modules`, `venv`, `__pycache__`, `*.md`, `docker-compose.yml`, временные файлы. Это ускорит сборку и предотвратит случайную утечку секретов.*
## Пример корректного Dockerfile (мини-шаблон)
*Для быстрого старта используй команду `docker init`, которая сгенерирует корректные Dockerfile, compose и .dockerignore под твой язык.*
Хорошо, вот интеграция примеров в раздел **Entrypoint** исходного скилла. Я добавил два конкретных скрипта (Alpine/`su-exec` и Ubuntu/`gosu`) и описал, как их встроить в Dockerfile. Также учтена exec-форма и рекомендации по сигналам.
## Entrypoint
Сделай кастомный Docker-Entrypoint, который запускает все скрипты из папки `/etc/docker-custom-init/*.sh`, сортируя их по имени.
Если нужно запустить само приложение, вызови `app`. Тогда entrypoint должен заменить вызов вызовом оригинального приложения.
**Обязательно используй exec-форму** в `ENTRYPOINT` и `CMD` запись вида `["executable", "param"]`, а не `executable param`. Только exec-форма гарантирует, что сигналы (SIGTERM, SIGINT) будут корректно переданы основному процессу и контейнер сможет правильно завершиться.
### Пример для Alpine (использует `su-exec`)
Скрипт `docker-entrypoint.sh` (универсален для Alpine, BusyBox, минимальных образов):
```sh
#!/bin/sh
set -e
INIT_DIRS="${DOCKER_INITD_DIRS:-/etc/init-custom-docker.d}"
SCRIPTS_ENVS="${DOCKER_INIT_SCRIPTS_ENVS}"
INIT_SCRIPT="${DOCKER_INIT_SCRIPT}"
log() {
echo "[init] $1"
}
# 1. Run init scripts from directories (as root)
for dir in $(echo "$INIT_DIRS" | tr ',' ' '); do
if [ -d "$dir" ]; then
log "Processing init directory: $dir"
for script in $(find "$dir" -maxdepth 1 -name '*.sh' -type f 2>/dev/null | sort); do
log "Running script: $script"
sh "$script"
done
else
log "Directory not found, skipping: $dir"
fi
done
# 2. Run scripts from environment variables (as root)
if [ -n "$SCRIPTS_ENVS" ]; then
for env_name in $(echo "$SCRIPTS_ENVS" | tr ',' ' '); do
eval "script_content=\$$env_name"
if [ -z "$script_content" ]; then
log "Error: Environment variable $env_name specified but empty"
exit 1
fi
log "Running scripts from environment: $env_name"
echo "$script_content" | sh
done
fi
# 3. Run script from DOCKER_INIT_SCRIPT (as root)
if [ -n "$INIT_SCRIPT" ]; then
log "Running script from DOCKER_INIT_SCRIPT"
echo "$INIT_SCRIPT" | sh
fi
# Switch to appuser using su-exec
log "Switching to user: appuser"
log "Starting application: $@"
exec su-exec appuser:appgroup "$@"
```
Как добавить в Dockerfile (Alpine):
```dockerfile
RUN apk add --no-cache su-exec # для Alpine
RUN addgroup -g 1000 appgroup && adduser -u 1000 -G appgroup -D appuser
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["app"]
```
### Пример для Ubuntu/Debian (использует `gosu`)
Скрипт `docker-entrypoint.sh` (аналог для Debian-based образов):
```bash
#!/bin/bash
set -e
INIT_DIRS="${DOCKER_INITD_DIRS:-/etc/init-custom-docker.d}"
SCRIPTS_ENVS="${DOCKER_INIT_SCRIPTS_ENVS}"
INIT_SCRIPT="${DOCKER_INIT_SCRIPT}"
APP_USER="${APP_USER:-appuser}"
APP_GROUP="${APP_GROUP:-appgroup}"
log() {
echo "[init] $1"
}
# 1. Execute scripts from directories (as root)
IFS=',' read -ra dirs <<< "$INIT_DIRS"
for dir in "${dirs[@]}"; do
if [ -d "$dir" ]; then
log "Processing init directory: $dir"
for script in $(find "$dir" -maxdepth 1 -name '*.sh' -type f 2>/dev/null | sort); do
log "Running script: $script"
bash "$script"
done
else
log "Directory not found, skipping: $dir"
fi
done
# 2. Execute scripts from environment variables (as root)
if [ -n "$SCRIPTS_ENVS" ]; then
IFS=',' read -ra env_names <<< "$SCRIPTS_ENVS"
for env_name in "${env_names[@]}"; do
script_content="${!env_name}"
if [ -z "$script_content" ]; then
log "Error: Environment variable $env_name specified but empty"
exit 1
fi
log "Running script from environment: $env_name"
echo "$script_content" | bash
done
fi
# 3. Execute script from DOCKER_INIT_SCRIPT (as root)
if [ -n "$INIT_SCRIPT" ]; then
log "Running script from DOCKER_INIT_SCRIPT"
echo "$INIT_SCRIPT" | bash
fi
# Switch to appuser using gosu
log "Switching to user: $APP_USER"
log "Starting application: $@"
exec gosu "${APP_USER}:${APP_GROUP}" "$@"
```
Как добавить в Dockerfile (Ubuntu):
```dockerfile
# Установка gosu (два варианта: через apt или копирование статического бинарника)
RUN apt-get update && apt-get install -y --no-install-recommends gosu && rm -rf /var/lib/apt/lists/*
# Или: COPY --from=gosu/alpine:latest /usr/local/bin/gosu /usr/local/bin/gosu
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["app"]
```
### Важные замечания
- Внутри скрипта обязательно используй **exec** в конце (например, `exec su-exec appuser "$@"` или `exec gosu appuser "$@"`), чтобы заменить процесс entrypoint на основной процесс приложения.
- Пути к init-директориям по умолчанию можно переопределить через переменные окружения (`DOCKER_INITD_DIRS`). Убедись, что нужные директории существуют (создай их в Dockerfile или смонтируй как тома).
- Чтобы уменьшить количество слоёв, можно объединить команды `RUN groupadd ... && useradd ... && apt-get install ...` в один `RUN`.
# Self-check список для валидации Dockerfile
Используй этот список для проверки своего Dockerfile перед финальной сборкой. Отмечай каждый пункт как выполненный.
## 1. Структура и многоступенчатость (Multistage)
- [ ] Есть ли разделение на этап сборки и этап запуска (если приложение требует компиляции/установки зависимостей)?
- [ ] Используется ли `COPY --from=` для переноса артефактов между стейджами?
- [ ] Объединены ли команды RUN в один слой (через `&&`) и очищен ли временный кэш (например, `rm -rf /var/lib/apt/lists/*`)?
- [ ] Используется ли `--mount=type=cache` для ускорения повторных сборок (если применимо)?
## 2. Параллелизм (BuildKit)
- [ ] Можно ли независимые стейджи выполнять параллельно? (Зависимости, тесты, документация – разделены?)
- [ ] Нет ли неявных зависимостей между параллельными стейджами?
## 3. Безопасность
- [ ] Версия базового образа фиксирована (например `python:3.11-slim-bookworm`) вместо `:latest`?
- [ ] Создан непривилегированный пользователь (`useradd -r -g appgroup appuser`)?
- [ ] Финальный процесс запускается от этого пользователя (`USER appuser`)?
- [ ] Файлы скопированы с `--chown=appuser:appgroup`?
- [ ] Используются флаги уменьшения размера: `--no-install-recommends` (apt), `--no-cache-dir` (pip), `--only=production` (npm)?
- [ ] Нет секретов в переменных `ENV` или слоях образа (используются `ARG`/секреты BuildKit)?
- [ ] Добавлен `HEALTHCHECK` для продакшен-контейнеров?
- [ ] Присутствуют метаданные `LABEL` (maintainer, version, description)?
## 4. Entrypoint
- [ ] Написан кастомный скрипт entrypoint, который запускает init-скрипты из `/etc/docker-custom-init/*.sh` (или из переменных окружения)?
- [ ] Entrypoint и CMD записаны в exec-форме (`["entrypoint.sh", "param"]`)?
- [ ] Внутри скрипта используется `exec` для передачи управления основному процессу (сигналы корректно обрабатываются)?
- [ ] Пользователь переключается на непривилегированного (через `gosu` или `su-exec`) в entrypoint?
## 5. .dockerignore
- [ ] Существует файл `.dockerignore` в корне проекта?
- [ ] В него включены: `.git`, `node_modules`, `venv`, `__pycache__`, `*.md`, `docker-compose.yml`, временные файлы?
## 6. Makefile
- [ ] Есть Makefile с таргетами для сборки, запуска, возможно `docker init`?
- [ ] В Dockerfile или инструкциях не используется прямая сборка через `docker build`, а только через `make`?
## 7. Docker Compose
- [ ] Есть файл `docker-compose.yml` (или `docker-compose.yaml`) для локального запуска?
- [ ] В compose корректно указаны порты, volumes, переменные окружения, не используется непривилегированный пользователь без необходимости?
## 8. Сканирование уязвимостей
- [ ] В CI/CD добавлен этап сканирования готового образа (trivy, docker scout, grype)?
- [ ] Уязвимости критического уровня отсутствуют или задокументированы?
## 9. Тестирование
- [ ] Dockerfile успешно собирается без ошибок?
- [ ] Контейнер запускается и отвечает на healthcheck (если задан)?
- [ ] Init-скрипты выполняются в правильном порядке?
---
### Пример быстрой проверки (через команды)
```bash
# 1. Проверка на секреты (не должно быть hardcoded паролей)
grep -n 'password\|secret\|key=' Dockerfile
# 2. Проверка exec-формы
grep -n '^ENTRYPOINT\|^CMD' Dockerfile | grep -v '^\['
# 3. Проверка наличия пользователя
grep -n '^RUN.*useradd\|^USER' Dockerfile
# 4. Проверка healthcheck
grep 'HEALTHCHECK' Dockerfile || echo "HEALTHCHECK отсутствует"
```