Рыба проекта. Минимальная функциональность

This commit is contained in:
2026-08-03 22:22:24 +03:00
commit 8c8631ac9c
80 changed files with 10618 additions and 0 deletions
+11
View File
@@ -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. |
+12
View File
@@ -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 отсутствует"
```