Рыба проекта. Минимальная функциональность
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Chore Skills
|
||||
|
||||
> **Русская версия:** [README.ru.md](README.ru.md)
|
||||
|
||||
Infrastructure and utility skills applicable across projects.
|
||||
|
||||
## Available Skills
|
||||
|
||||
| Skill | Description |
|
||||
|-------|-------------|
|
||||
| [dockerfile-skill](dockerfile-skill) | Best practices for writing production-ready Dockerfiles: multistage builds, BuildKit parallelism, security hardening (non-root user, pinned base images, no secrets in layers), healthchecks, custom entrypoints, `.dockerignore`, and integration with Makefile / Docker Compose. Includes a self-check checklist. |
|
||||
@@ -0,0 +1,12 @@
|
||||
# Chore Skills
|
||||
|
||||
> **English version:** [README.md](README.md)
|
||||
> **Вернуться к оглавлению:** [README.ru.md](../../README.ru.md)
|
||||
|
||||
Инфраструктурные и утилитарные скиллы, применимые во всех проектах.
|
||||
|
||||
## Доступные скиллы
|
||||
|
||||
| Скилл | Описание |
|
||||
|-------|----------|
|
||||
| [dockerfile-skill](dockerfile-skill) | Лучшие практики написания production-ready Dockerfile: многоступенчатая сборка, параллельные стейджи BuildKit, харднинг безопасности (непривилегированный пользователь, фиксированные версии образов, отсутствие секретов в слоях), healthchecks, кастомные entrypoint-скрипты, `.dockerignore` и интеграция с Makefile / Docker Compose. Включает чеклист самопроверки. |
|
||||
@@ -0,0 +1,289 @@
|
||||
---
|
||||
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 отсутствует"
|
||||
```
|
||||
Reference in New Issue
Block a user