Главная/Containers и lifecycle/Практика

Раздел 4. Практические задания

Задания выполняются в реальной системе. Разбор открывайте только после самостоятельной попытки.

Обозначения: [обяз.] — обязательное, [доп.] — дополнительное, [★] — повышенной сложности, [диаг.] — диагностическое.

Подготовка:

bash
mkdir -p ~/docker-course/04-lifecycle
cd ~/docker-course/04-lifecycle
# docker pull принимает РОВНО один образ:
# `docker pull a b` отвечает «docker pull requires 1 argument»
for img in python:3.13-slim alpine:3.21; do docker pull -q "$img"; done

Задание 1. Все состояния container [обяз.]

Постановка. Проведите один container через все доступные состояния и зафиксируйте на каждом шаге: вывод docker ps -a, значение .State.Status, PID процесса.

Последовательность: createstartpauseunpausestopstartrm.

Ожидаемый результат. Таблица из семи строк. PID при первом и втором start должен различаться.

Проверка:

bash
docker create --name st alpine sleep 300 > /dev/null
docker inspect st --format '{{.State.Status}} PID={{.State.Pid}}'
docker start st > /dev/null
docker inspect st --format '{{.State.Status}} PID={{.State.Pid}}'
docker rm -f st > /dev/null

Полное решение — в уроке 4.1.


Задание 2. Exec и его свойства [обяз.]

Постановка. Запустите долгоживущий container и через docker exec:

  1. Выполните команду и покажите, что процесс не имеет PID 1.
  2. Завершите exec-процесс с кодом 1 и убедитесь, что container продолжает работать.
  3. Покажите, что переменная, созданная entrypoint-скриптом, в exec не видна, а переменная из ENV — видна.
  4. Прочитайте фактическое окружение PID 1 обходным путём.

Ожидаемый результат. Вывод ps с PID больше единицы и демонстрация обоих случаев с переменными.

Разбор
bash
cat > entrypoint.sh <<'SH'
#!/bin/sh
export RUNTIME_VAR="создан-в-entrypoint"
exec "$@"
SH
chmod +x entrypoint.sh

cat > Dockerfile.env <<'EOF'
FROM alpine:3.21
ENV DOCKERFILE_VAR="объявлен через ENV"
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["sleep", "300"]
EOF

docker build -q -f Dockerfile.env -t ex2:1 . > /dev/null
docker run -d --name ex2 ex2:1 > /dev/null
sleep 1

echo "=== 1. PID процесса exec ==="
docker exec ex2 ps -o pid,ppid,args

echo
echo "=== 2. Завершение exec с кодом 1 ==="
docker exec ex2 sh -c 'exit 1'
echo "код exec: $?"
docker ps --filter name=ex2 --format 'container: {{.Status}}'

echo
echo "=== 3. Переменные ==="
docker exec ex2 sh -c '
  echo "DOCKERFILE_VAR: ${DOCKERFILE_VAR:-НЕ ВИДНА}"
  echo "RUNTIME_VAR:    ${RUNTIME_VAR:-НЕ ВИДНА}"
'

echo
echo "=== 4. Реальное окружение PID 1 ==="
docker exec ex2 sh -c "tr '\0' '\n' < /proc/1/environ | grep -E 'RUNTIME_VAR|DOCKERFILE_VAR'"

docker rm -f ex2 > /dev/null
docker rmi ex2:1 > /dev/null
text
=== 1. PID процесса exec ===
PID   PPID  COMMAND
    1     0 sleep 300
    8     0 ps -o pid,ppid,args

=== 2. Завершение exec с кодом 1 ===
код exec: 1
container: Up 2 seconds

=== 3. Переменные ===
DOCKERFILE_VAR: объявлен через ENV
RUNTIME_VAR:    НЕ ВИДНА

=== 4. Реальное окружение PID 1 ===
DOCKERFILE_VAR=объявлен через ENV
RUNTIME_VAR=создан-в-entrypoint

Разбор наблюдений.

Процесс ps получил PID 8 и PPID 0. Нулевой родитель означает, что процесс порождён извне PID namespace — через setns(), а не как потомок PID 1.

Завершение exec с кодом 1 не затронуло container: он остался в состоянии Up. Это принципиальное отличие от attach, где сигнал может остановить главный процесс.

Переменная DOCKERFILE_VAR видна, потому что она записана в конфигурацию образа и Docker добавляет её в окружение любого процесса container, включая exec. Переменная RUNTIME_VAR создана командой export внутри процесса entrypoint — это часть окружения конкретного процесса, и новый процесс её не наследует.

Чтение /proc/1/environ показывает фактическое окружение работающего приложения. Это основной приём диагностики, когда нужно понять, с какими переменными реально работает сервис, — например, при подозрении, что переменная не дошла до приложения.


Задание 3. Graceful shutdown [обяз.]

Постановка. Напишите Python-приложение, которое обрабатывает SIGTERM, и докажите измерением, что docker stop завершает его корректно.

Требования к доказательству:

  1. Время docker stop меньше секунды.
  2. Exit code равен 0.
  3. В логах присутствуют строки, которые печатает только обработчик.

Затем удалите обработчик и покажите, как изменятся все три показателя.

Ожидаемый результат. Две серии измерений с объяснением разницы.

Полное решение — в уроке 4.4.


Задание 4. Влияние формы CMD [обяз.]

Постановка. Соберите два образа с одинаковым Python-кодом (обработчик SIGTERM установлен), различающихся только формой записи CMD: shell form и exec form.

Покажите:

  1. Дерево процессов в каждом случае.
  2. Время docker stop и exit code.
  3. Дошёл ли сигнал до приложения.

Затем почините shell-form вариант, не переходя на exec form.

Ожидаемый результат. Таблица сравнения трёх конфигураций и рабочее исправление.

Проверка:

bash
docker exec <container> ps -o pid,args | awk '$1==1'
Разбор

Полная реализация — в уроке 4.4. Ключевые выводы:

Форма CMDPID 1Время stopКодСигнал дошёл
CMD python -u /app.py/bin/sh -c ...10.3 c137нет
CMD ["python", "-u", "/app.py"]python0.4 c0да
CMD exec python -u /app.pypython0.4 c0да

Как чинится shell form. Встроенная команда exec заменяет процесс оболочки процессом приложения — вызывается execve() без порождения нового процесса. sh не остаётся в памяти, приложение становится PID 1.

Это нужно, когда возможности оболочки действительно требуются:

dockerfile
FROM python:3.13-slim
COPY app.py /app/app.py
ENV APP_MODULE=/app/app.py
CMD exec python -u "$APP_MODULE"

Без exec подстановка $APP_MODULE тоже сработает, но sh останется PID 1 и сигнал не дойдёт.

Тот же приём обязателен в entrypoint-скриптах: последняя строка должна быть exec "$@", а не "$@".

Практический критерий проверки чужого образа:

bash
docker exec <container> ps -o pid,args | awk '$1==1'

Если PID 1 — это /bin/sh -c ..., graceful shutdown работать не будет независимо от качества кода приложения.


Задание 5. Zombie и --init [доп.]

Постановка. Воспроизведите накопление zombie-процессов и устраните проблему флагом --init.

  1. Напишите приложение, порождающее процессы, которые оставляют сирот.
  2. Покажите zombie в ps без --init.
  3. Покажите их отсутствие с --init.
  4. Объясните, почему с --init меняется exit code при docker stop для приложения без обработчика.

Проверка:

bash
docker exec <container> sh -c "ps -o stat= | grep -c '^Z'"

Полное решение — в уроке 4.5.


Задание 6. Exit codes 125, 126, 127 [доп.]

Постановка. Воспроизведите каждый из трёх кодов и объясните, чем причины принципиально различаются.

Для каждого укажите: какая часть системы виновата и как исправлять.

Разбор
bash
echo "=== 125 ==="
docker run --rm --invalid-flag alpine echo x 2>&1 | head -1
echo "код: $?"

echo
echo "=== 126 ==="
printf '#!/bin/sh\necho hi\n' > script.sh
chmod 644 script.sh          # намеренно без бита исполнения
docker run --rm -v "$PWD/script.sh:/s.sh:ro" alpine /s.sh 2>&1 | tail -1
echo "код: $?"

echo
echo "=== 127 ==="
docker run --rm alpine bash -c 'echo x' 2>&1 | tail -1
echo "код: $?"
text
=== 125 ===
unknown flag: --invalid-flag
код: 125

=== 126 ===
docker: Error response from daemon: ... permission denied
код: 126

=== 127 ===
docker: Error response from daemon: ... exec: "bash": executable file not found in $PATH
код: 127

Принципиальное различие — на каком этапе произошёл сбой.

КодЭтапКто виноватКак исправлять
125До создания containerКоманда dockerИсправить флаги; образ ни при чём
126При запуске процессаПрава на файлchmod +x, проверить shebang и переводы строк
127При поиске файлаОтсутствие файла или интерпретатораИсправить путь или доустановить зависимость

Проверить теорию для 126:

bash
chmod +x script.sh
docker run --rm -v "$PWD/script.sh:/s.sh:ro" alpine /s.sh
text
hi

Второй частый источник 126 — файлы с переводами строк CRLF, созданные в Windows. Shebang #!/bin/sh\r содержит символ возврата каретки, и ядро ищет интерпретатор с именем /bin/sh\r, которого нет. Проверить:

bash
file script.sh

Ожидается POSIX shell script, ASCII text executable. Наличие with CRLF line terminators — источник проблемы; лечится dos2unix или настройкой .gitattributes.

Для 127 самый частый случай в реальных проектах — bash в Alpine. В образе есть только sh из BusyBox:

bash
docker run --rm alpine sh -c 'echo работает'
docker run --rm alpine sh -c 'ls -l /bin/sh; command -v bash || echo "bash отсутствует"'
text
работает
lrwxrwxrwx    1 root     root    12 Jun 14 02:11 /bin/sh -> /bin/busybox
bash отсутствует

Задание 7. Restart policies [доп.]

Постановка. Сравните поведение всех четырёх политик в двух сценариях: завершение с кодом 0 и с кодом 1.

Затем покажите единственное различие между always и unless-stopped — потребуется перезапуск daemon.

Ожидаемый результат. Таблица из восьми ячеек плюс демонстрация различия.

Перезапуск daemon остановит все ваши containers, если не включён live-restore. Выполняйте на учебной машине.

Разбор
bash
test_policy() {
    local policy="$1" code="$2"
    local name="rp-${policy//:/-}-$code"
    docker run -d --name "$name" --restart="$policy" \
        alpine sh -c "sleep 1; exit $code" > /dev/null 2>&1
    sleep 8
    printf '  %-18s код %s -> перезапусков: %s, статус: %s\n' \
        "$policy" "$code" \
        "$(docker inspect "$name" --format '{{.RestartCount}}')" \
        "$(docker inspect "$name" --format '{{.State.Status}}')"
    docker rm -f "$name" > /dev/null 2>&1
}

echo "=== Реакция на код возврата ==="
for p in no on-failure:3 always unless-stopped; do
    test_policy "$p" 0
    test_policy "$p" 1
done
text
=== Реакция на код возврата ===
  no                 код 0 -> перезапусков: 0, статус: exited
  no                 код 1 -> перезапусков: 0, статус: exited
  on-failure:3       код 0 -> перезапусков: 0, статус: exited
  on-failure:3       код 1 -> перезапусков: 3, статус: exited
  always             код 0 -> перезапусков: 3, статус: restarting
  always             код 1 -> перезапусков: 3, статус: restarting
  unless-stopped     код 0 -> перезапусков: 3, статус: restarting
  unless-stopped     код 1 -> перезапусков: 3, статус: restarting

Единственная строка, где on-failure отличается от always, — код 0: успешное завершение не считается сбоем.

Различие always и unless-stopped:

bash
docker run -d --name a1 --restart=always alpine sleep 3600 > /dev/null
docker run -d --name u1 --restart=unless-stopped alpine sleep 3600 > /dev/null
sleep 2
docker stop a1 u1 > /dev/null

sudo systemctl restart docker
sleep 5

docker ps -a --filter 'name=a1' --filter 'name=u1' \
    --format 'table {{.Names}}\t{{.Status}}'
docker rm -f a1 u1 > /dev/null
text
NAMES   STATUS
u1      Exited (137) 25 seconds ago
a1      Up 4 seconds

always поднял вручную остановленный container, unless-stopped — нет.

Почему это важно. Сценарий из практики: сервис остановлен для планового обслуживания, ночью сервер перезагружается по обновлению ядра. С always сервис поднимется в неконсистентном состоянии посреди обслуживания. С unless-stopped — останется остановленным, как и было решено.

Поэтому для сервисов на серверах предпочтителен unless-stopped.


Задание 8. Диагностика: 10 секунд на остановку [диаг.]

Постановка. Дан container, который останавливается ровно 10 секунд с кодом 137. Есть две независимые причины такого поведения.

  1. Назовите обе.
  2. Воспроизведите каждую по отдельности.
  3. Предложите проверку, однозначно различающую их.
  4. Исправьте обе.
Подсказка

Одна причина связана с формой записи CMD, другая — со свойством PID 1, о котором говорилось в уроке 4.5.

Разбор

Причина A: приложение не является PID 1 — между Docker и приложением стоит оболочка из-за shell form.

Причина B: приложение является PID 1, но не имеет обработчика SIGTERM — ядро игнорирует сигналы без обработчика для PID 1.

Обе дают идентичный наблюдаемый результат: 10 секунд, код 137.

bash
cat > app_h.py <<'PY'
import signal, sys, time
def h(s, f):
    print("HANDLER", flush=True); sys.exit(0)
signal.signal(signal.SIGTERM, h)
print("started", flush=True)
while True: time.sleep(0.2)
PY

cat > app_nh.py <<'PY'
import time
print("started", flush=True)
while True: time.sleep(0.2)
PY

# A: обработчик ЕСТЬ, но shell form
cat > Dockerfile.A <<'EOF'
FROM python:3.13-slim
COPY app_h.py /app.py
CMD python -u /app.py
EOF

# B: exec form, но обработчика НЕТ
cat > Dockerfile.B <<'EOF'
FROM python:3.13-slim
COPY app_nh.py /app.py
CMD ["python", "-u", "/app.py"]
EOF

docker build -q -f Dockerfile.A -t diag:a . > /dev/null
docker build -q -f Dockerfile.B -t diag:b . > /dev/null

for v in a b; do
    docker run -d --name "d-$v" "diag:$v" > /dev/null
    sleep 1
    s="$(date +%s.%N)"; docker stop "d-$v" > /dev/null; e="$(date +%s.%N)"
    printf 'вариант %s: %.1f c, код %s\n' "$v" \
        "$(awk -v a="$s" -v b="$e" 'BEGIN{print b-a}')" \
        "$(docker inspect "d-$v" --format '{{.State.ExitCode}}')"
    docker rm "d-$v" > /dev/null
done
text
вариант a: 10.3 c, код 137
вариант b: 10.3 c, код 137

Симптом идентичен.

Различающая проверка — кто является PID 1:

bash
for v in a b; do
    docker run -d --name "d-$v" "diag:$v" > /dev/null
    sleep 1
    printf 'вариант %s, PID 1: %s\n' "$v" \
        "$(docker exec "d-$v" ps -o args= -p 1 2>/dev/null | head -1)"
    docker rm -f "d-$v" > /dev/null
done
text
вариант a, PID 1: /bin/sh -c python -u /app.py
вариант b, PID 1: python -u /app.py

Диагноз однозначен: /bin/sh в PID 1 — причина A; собственно приложение — причина B.

Универсальная процедура диагностики:

text
   docker stop занимает 10 секунд, код 137
              │
              ▼
   docker exec <c> ps -o args= -p 1
              │
     ┌────────┴────────┐
     │                 │
  /bin/sh -c …     приложение
     │                 │
     ▼                 ▼
  ПРИЧИНА A        ПРИЧИНА B
  shell form       нет обработчика
     │                 │
     ▼                 ▼
  exec form        signal.signal(SIGTERM, …)
  или exec в CMD

Исправление обеих:

bash
cat > Dockerfile.fixed <<'EOF'
FROM python:3.13-slim
COPY app_h.py /app.py
CMD ["python", "-u", "/app.py"]
EOF

docker build -q -f Dockerfile.fixed -t diag:fixed . > /dev/null
docker run -d --name d-fixed diag:fixed > /dev/null
sleep 1
s="$(date +%s.%N)"; docker stop d-fixed > /dev/null; e="$(date +%s.%N)"
printf 'исправлено: %.2f c, код %s\n' \
    "$(awk -v a="$s" -v b="$e" 'BEGIN{print b-a}')" \
    "$(docker inspect d-fixed --format '{{.State.ExitCode}}')"
docker logs d-fixed | tail -1
docker rm d-fixed > /dev/null
docker rmi diag:a diag:b diag:fixed > /dev/null
text
исправлено: 0.42 c, код 0
HANDLER

Обе причины устранены: exec form обеспечивает доставку сигнала, обработчик обеспечивает корректное завершение. Нужны оба условия — исправление только одного оставит проблему.


Задание 9. Restart loop [диаг.]

Постановка. Создайте container с политикой always, падающий каждые несколько секунд. Затем:

  1. Определите, что это restart loop, по трём независимым признакам.
  2. Найдите причину, не изменяя приложение.
  3. Разорвите цикл, не удаляя container.
  4. Объясните, почему при падении раз в 15 секунд backoff может не наблюдаться.
Разбор
bash
cat > flaky.py <<'PY'
import os, sys, time
n = int(os.environ.get("LIFETIME", "3"))
print(f"старт, проживу {n} c", flush=True)
time.sleep(n)
print("критическая ошибка: не найдена конфигурация", file=sys.stderr, flush=True)
sys.exit(1)
PY

docker run -d --name loop --restart=always -e LIFETIME=3 \
    -v "$PWD/flaky.py:/app.py:ro" \
    python:3.13-slim python -u /app.py > /dev/null
sleep 20

Три признака restart loop:

bash
echo "1. Статус в docker ps:"
docker ps -a --filter name=loop --format '   {{.Status}}'

echo "2. Счётчик перезапусков (два замера):"
echo "   $(docker inspect loop --format '{{.RestartCount}}')"
sleep 6
echo "   $(docker inspect loop --format '{{.RestartCount}}') — вырос"

echo "3. События:"
docker events --since 30s --until 0s --filter container=loop \
    --format '   {{.Action}}' 2>/dev/null | tail -6
text
1. Статус в docker ps:
   Restarting (1) 2 seconds ago
2. Счётчик перезапусков (два замера):
   5
   6 — вырос
3. События:
   start
   die
   start
   die
   start
   die

Каждый признак самостоятелен. Второй — самый надёжный: статус в docker ps может застать container в момент Up, а счётчик растёт монотонно.

Поиск причины:

bash
docker logs loop 2>&1 | tail -4
text
старт, проживу 3 c
критическая ошибка: не найдена конфигурация
старт, проживу 3 c
критическая ошибка: не найдена конфигурация

Логи сохраняются между перезапусками, и причина видна прямо в них.

Разрыв цикла:

bash
docker update --restart=no loop
sleep 5
docker ps -a --filter name=loop --format '{{.Status}}'
text
Exited (1) 2 seconds ago

Container остановился и больше не поднимается. Теперь можно спокойно разбираться: логи не затираются, состояние стабильно, можно запустить docker exec для осмотра.

Это первое действие при разборе restart loop в production — иначе диагностика идёт наперегонки с перезапусками.

Почему backoff может не наблюдаться:

bash
docker rm -f loop > /dev/null
docker run -d --name loop15 --restart=always -e LIFETIME=15 \
    -v "$PWD/flaky.py:/app.py:ro" \
    python:3.13-slim python -u /app.py > /dev/null

echo "интервалы для приложения, живущего 15 с:"
timeout 45 docker events --filter container=loop15 \
    --format '{{.Time}} {{.Action}}' 2>/dev/null | grep -E 'die|start'
docker rm -f loop15 > /dev/null
text
1785409201 start
1785409216 die
1785409216 start
1785409231 die
1785409231 start

Задержка между die и start нулевая. Причина: Docker сбрасывает счётчик backoff, если container проработал более 10 секунд. Приложение укладывалось в 15 секунд, поэтому каждый перезапуск считался «первым».

Практический вывод. Отсутствие нарастающей задержки не означает отсутствия проблемы. Более того, такой restart loop опаснее: в docker ps он большую часть времени выглядит как Up, и мониторинг по признаку «container запущен» его не заметит. Отслеживать нужно RestartCount.

bash
docker rmi -f $(docker images -q --filter reference='diag:*') 2>/dev/null || true

Задание 10. Полная диагностика [★]

Постановка. Напишите скрипт diagnose-exit.sh <container>, который по завершившемуся container выдаёт готовый диагноз с рекомендацией.

Требования:

  1. Расшифровка exit code, включая 125, 126, 127 и 128+N.
  2. Для 137 — различение OOM, docker kill и истёкшего grace period.
  3. Обнаружение restart loop по RestartCount и статусу.
  4. Последние события и логи.
  5. Конкретная рекомендация для каждого случая.

Скрипт должен корректно работать для container в любом состоянии.

Полное решение — в уроке 4.6.


Задание 11. Ожидание завершения в скрипте [★]

Постановка. Напишите функцию run_task, которая запускает разовую задачу в detached режиме, дожидается её завершения и возвращает её exit code — так, чтобы вызывающий скрипт мог корректно на него отреагировать.

Требования:

  • ограничение по времени: если задача не завершилась за N секунд, остановить её и вернуть отдельный код;
  • container удаляется в любом случае, включая прерывание скрипта;
  • логи задачи выводятся при ненулевом коде.

Объясните, почему нельзя просто использовать docker run без -d.

Подсказка 1

docker wait блокируется до завершения и печатает exit code. Ограничить его по времени можно через timeout.

Подсказка 2

Гарантированная уборка — через trap ... EXIT или локальную ловушку внутри функции.

Разбор
bash
#!/usr/bin/env bash
# run_task — запуск разовой задачи с таймаутом и корректным exit code.
set -uo pipefail

run_task() {
    local timeout_sec="$1"; shift
    local name="task-$$-$RANDOM"
    local code

    if ! docker run -d --name "$name" "$@" > /dev/null 2>&1; then
        echo "не удалось запустить задачу" >&2
        return 125
    fi

    if code="$(timeout "$timeout_sec" docker wait "$name" 2>/dev/null)"; then
        : # задача завершилась сама
    else
        echo "задача не уложилась в ${timeout_sec} c — останавливаю" >&2
        docker stop "$name" > /dev/null 2>&1
        docker rm -f "$name" > /dev/null 2>&1
        return 124
    fi

    if [ "${code:-1}" -ne 0 ]; then
        echo "--- логи задачи (код $code) ---" >&2
        docker logs "$name" 2>&1 | sed 's/^/  /' >&2
    fi

    docker rm -f "$name" > /dev/null 2>&1
    return "${code:-1}"
}

echo "=== 1. Успешная задача ==="
run_task 10 alpine sh -c 'echo работаю; exit 0'
echo "код: $?"

echo
echo "=== 2. Задача с ошибкой ==="
run_task 10 alpine sh -c 'echo что-то пошло не так >&2; exit 3'
echo "код: $?"

echo
echo "=== 3. Задача, превысившая таймаут ==="
run_task 3 alpine sleep 60
echo "код: $?"
text
=== 1. Успешная задача ===
код: 0

=== 2. Задача с ошибкой ===
--- логи задачи (код 3) ---
  что-то пошло не так
код: 3

=== 3. Задача, превысившая таймаут ===
задача не уложилась в 3 c — останавливаю
код: 124

Почему не подходит просто docker run без -d.

На первый взгляд docker run --rm alpine cmd; echo $? решает задачу: exit code проходит наружу. Но три требования не выполняются.

Таймаут. Обернуть в timeout можно, но при срабатывании будет убит клиент docker, а не container. Container останется работать, и без --rm он ещё и останется в системе. С --rm он будет удалён после завершения, но само завершение может не наступить никогда.

Гарантированная уборка. При прерывании скрипта между запуском и завершением container остаётся. С detached режимом и явным docker rm в ловушке этого не происходит.

Логи при ошибке. В foreground режиме вывод уже смешался с выводом скрипта. В detached режиме логи можно получить отдельно и показать только при ненулевом коде.

Дополнительно detached режим позволяет запустить несколько задач параллельно и дождаться всех:

bash
for t in a b c; do
    docker run -d --name "batch-$t" alpine sh -c "sleep 2; echo $t" > /dev/null
done
for t in a b c; do
    printf '%s: код %s\n' "$t" "$(docker wait "batch-$t")"
    docker rm "batch-$t" > /dev/null
done
text
a: код 0
b: код 0
c: код 0

Код 124 для таймаута выбран по соглашению утилиты timeout — она возвращает именно его. Это делает поведение функции предсказуемым для тех, кто знаком с timeout.


Очистка после раздела

bash
docker ps -aq --filter 'name=task-' | xargs -r docker rm -f
docker ps -aq --filter 'name=batch-' | xargs -r docker rm -f
docker ps -aq --filter 'status=exited' | xargs -r docker rm
docker image prune -f
docker system df

Критерии завершения

Раздел закрыт, когда выполнены обязательные задания 1–4 и вы можете без подсказок ответить на вопросы из MAIN.md раздела.

Дальше: Quiz 04 и Checkpoint 1.


Навигация

← Предыдущий материал
Вернуться к разделу
Следующий раздел → Dockerfile
Главное оглавление

Markdown на GitHub ↗