---  
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 отсутствует"
```
