Урок курса
Healthcheck и готовность базы
Docker и Docker Compose: контейнеризация приложений1. Процесс работает, база ещё готовится
В двухконтейнерном стеке app и db запуск процесса операционной системой не означает немедленную готовность приложения отвечать на запросы. Когда Docker запускает контейнер с PostgreSQL, процесс postgres стартует сразу, однако серверу требуется время: прочитать конфигурацию, восстановить файлы данных, а при первом запуске — выполнить служебную процедуру инициализации каталога (initdb).
Выданное приложение course-notes спроектировано устойчиво к сетевым задержкам: режим python -u app.py serve поднимает HTTP-сервер на порту 8000 даже тогда, когда PostgreSQL ещё не отвечает. Контейнер переходит в статус Up, маршрут проверки процесса GET /health возвращает код 200 с телом {"status": "alive"}. Но при обращении к маршруту проверки готовности хранилища GET /ready приложение пытается выполнить проверочный запрос к базе данных. Если сокет базы ещё не принимает клиентские соединения или в базе ещё не создана требуемая таблица схемы, запрос возвращает код 503 ({"status": "not_ready"}).
Для проверки доступности PostgreSQL существует стандартная системная утилита pg_isready, входящая в образ postgres:17.4-bookworm. Она проверяет, готов ли сервер принимать подключения, не выполняя при этом тяжёлых пользовательских SQL-запросов и не требуя передачи пароля.
Контракт кодов возврата утилиты pg_isready:
0— сервер принимает подключения и готов к работе;1— сервер отклоняет подключения (например, завершает стартовую инициализацию или находится в режиме восстановления);2— сервер не отвечает (порт закрыт, процесс не запущен);3— попытка подключения не предпринималась (переданы синтаксически некорректные параметры или не заданы обязательные флаги вызова).
Проверка отвечает только на свой вопрос. Утилита pg_isready -h 127.0.0.1 -p 5432 -U course -d course проверяет исключительно способность сетевого демона СУБД принять входящее TCP-соединение на указанном порту. Сервер вернёт код 0, даже если указаны несуществующие имя пользователя или базы данных, поскольку pg_isready не выполняет аутентификацию. Соответственно, код 0 не гарантирует, что пароль приложения верен, что в базе развёрнуты таблицы или что не исчерпан лимит прикладных сессий. Корректность учётных данных и наличие схемы проверяются прикладными командами init/check и запросом к /ready.
Периодическое исполнение такой проверочной команды внутри контейнера называется механизмом healthcheck. Демон Docker отслеживает код возврата команды и присваивает контейнеру статус здоровья:
starting— контейнер только что запущен, первая проверка ещё выполняется либо идёт начальный льготный интервал;healthy— команда проверки завершилась с кодом0;unhealthy— команда завершилась с ненулевым кодом подряд заданное количество раз (retries).
Текущий статус отображается в выводе списка контейнеров рядом с временем работы: например, Up 10 seconds (health: starting) или Up 45 seconds (healthy), а полная история последних запусков проверки записывается в блок .State.Health вывода docker inspect.
Интерактивная модель готовности сервисов
Скачать docker_readiness_model.zip
Распакуйте архив и откройте readiness_lab.html в браузере. Это автономная учебная модель; Docker она не запускает.
2. Дождаться проверки зависимости
По умолчанию директива depends_on: [db] в Compose определяет только порядок запуска контейнеров: демон Docker стартует db и сразу же запускает app, не дожидаясь готовности сетевого сокета PostgreSQL. Чтобы связать запуск процесса приложения с фактической готовностью базы данных, в файле конфигурации используется расширенная форма depends_on с условием condition: service_healthy.
Механизм проверки можно задать как в Dockerfile через инструкцию HEALTHCHECK, так и напрямую в секции сервиса внутри compose.yaml. Определение проверки в Compose позволяет настраивать параметры под конкретное окружение и сервисы без пересборки базовых образов.
Перед запуском убедитесь, что хостовый порт 18126 свободен, а ресурсы нового учебного проекта с префиксом cf_docker_course_l26 ещё не существуют в системе:
docker container ls -a --filter name=cf_docker_course_l26
docker network ls --filter name=cf_docker_course_l26
docker volume ls --filter name=cf_docker_course_l26
В каталоге проекта подготовьте файл compose.yaml со связкой сервисов, выделенным томом db_data и портом 18126:
services:
db:
image: postgres:17.4-bookworm
environment:
POSTGRES_DB: course
POSTGRES_USER: course
POSTGRES_PASSWORD: secret
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-p", "5432", "-U", "course", "-d", "course"]
interval: 3s
timeout: 3s
retries: 20
start_period: 10s
app:
image: course-notes:1.0.0
ports:
- "127.0.0.1:18126:8000"
environment:
STORAGE: postgres
DB_HOST: db
DB_PORT: "5432"
DB_NAME: course
DB_USER: course
PASSWORD: secret
depends_on:
db:
condition: service_healthy
volumes:
db_data:
Параметры блока healthcheck задают правила проверки:
test— исполняемая команда. Список["CMD", ...]вызывает бинарник напрямую без шелла. Явный хост-h 127.0.0.1исключает ложную готовность: во времяinitdbPostgreSQL запускает временный сервер только на Unix-сокете, поэтому без флага-h 127.0.0.1проверка сочла бы базу готовой ещё до открытия внешнего TCP-порта 5432;interval— интервал между повторными проверками (3 секунды);timeout— лимит времени ожидания ответа на одну попытку (3 секунды);retries— порог последовательных ошибок (exit code не 0) до перехода в статусunhealthy;start_period— начальный период (10 секунд), во время которого неудачные проверки не расходуют счетчикretries. Еслиpg_isreadyвернёт 0 в пределах этого окна, статусhealthyустановится сразу.
Запустите проект в первом терминале на переднем плане:
docker compose -p cf_docker_course_l26 up
Важно различать создание контейнера и запуск его главного процесса. Если во втором терминале выполнить docker compose -p cf_docker_course_l26 ps -a прямо в момент подъёма стека, можно увидеть, что контейнер app уже создан (Created), но его процесс ещё не стартует. Compose ожидает, пока контейнер db пройдёт проверку готовности: цепочка статусов в логе выглядит как app Created → db Waiting → db Healthy → app Starting. Условие condition: service_healthy блокирует именно старт (Starting/Running) зависимого сервиса, а не факт создания контейнера демоном.
Если запустить проект в фоновом режиме через docker compose -p cf_docker_course_l26 up -d, команда завершится только после того, как db получит статус healthy, а app запустится. Проверьте состояние сервисов:
docker compose -p cf_docker_course_l26 ps
В столбце STATUS база db имеет статус Up ... (healthy), а сервис app находится в рабочем состоянии Up. Для сервиса app отдельный healthcheck в этом манифесте не задан, поэтому статус здоровья отображается только у СУБД.
Убедитесь по логам приложения, что сервер начал принимать входящие запросы:
docker compose -p cf_docker_course_l26 logs app
В логе должна появиться запись started с параметрами service: course-notes и storage: postgres. Теперь проверьте маршруты с хоста через опубликованный порт 18126:
curl -fsS http://127.0.0.1:18126/health
Ответ {"status": "alive"} подтверждает, что HTTP-процесс активен. Далее проверьте маршрут проверки хранилища:
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18126/ready
Запрос вернёт код 503. База данных принимает TCP-подключения, но на новом томе db_data отсутствует схема таблиц приложения. Выполните одноразовую команду инициализации структуры данных:
docker compose -p cf_docker_course_l26 exec app python app.py init
Команда возвращает {"status": "initialized", "schema": 1} с кодом выхода 0. Повторный запрос curl -fsS http://127.0.0.1:18126/ready вернёт код 200 и тело {"status": "ready"}.
3. Что проверка не исправляет
Статус unhealthy является исключительно информационным свойством в метаданных рантайма Docker. Сам по себе переход проверки в неуспешное состояние не заставляет демон Docker перезапускать процесс, останавливать контейнер или заменять его новым экземпляром.
Поведение демона привязано к жизненному циклу процесса: стандартные механизмы перезапуска реагируют только на фактическое завершение основного процесса в ОС (появление кода выхода). Если процесс продолжает выполняться, но регулярная команда healthcheck завершается ошибкой, контейнер остаётся активным в статусе Up (unhealthy).
Это подтверждает контрольное наблюдение жизненного цикла контейнера с заведомо проваленной проверкой:
{
"Id": "7c82c4db8c0322c8c50eb2f061d30f8af8a3fa63a7a2e865eca9f1d83d664325",
"State": {
"Running": true,
"Health": {
"Status": "unhealthy"
}
},
"RestartCount": 0
}
Обратите внимание на три поля: идентификатор контейнера Id не изменился, поле Running остаётся true (главный процесс всё ещё исполняется), статус проверки перешёл в unhealthy, а счётчик перезапусков RestartCount равен 0. Демон зафиксировал поломку контракта проверки, но не предпринял попыток принудительного перезапуска.
Второе важное ограничение касается связки в Compose: директива depends_on: { condition: service_healthy } управляет только начальным порядком развёртывания стека при docker compose up. Она не превращается в постоянный мониторинг топологии после запуска и не переводит зависимые сервисы в аварийный режим, если соседний контейнер вышел из строя.
Проверьте это на запущенном проекте текущего урока. Во втором терминале остановите контейнер базы данных:
docker compose -p cf_docker_course_l26 stop db
Запросите статус сервисов:
docker compose -p cf_docker_course_l26 ps -a
Сервис db перешёл в статус Exited, однако сервис app продолжает работать в статусе Up. Compose не остановил и не перезапустил app вслед за упавшей зависимостью.
Запрос к маршрутам приложения через порт 18126 подтверждает независимость процессов:
curl -fsS http://127.0.0.1:18126/health
{"status": "alive"}
Маршрут /health возвращает код 200, поскольку сам процесс веб-сервера жив. Но проверка хранилища через маршрут /ready зафиксирует потерю связи:
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18126/ready
Команда возвращает 503, так как база остановлена и TCP-порт 5432 недоступен. При этом веб-процесс не падает и сохраняет способность обрабатывать входящие HTTP-запросы.
После завершения экспериментов остановите и удалите учебный проект, сохранив том с данными базы:
docker compose -p cf_docker_course_l26 down
Таким образом, healthcheck сообщает статус работоспособности внешним наблюдателям, а condition: service_healthy упорядочивает первоначальный старт. Автоматическое восстановление разорванных соединений во время работы приложения остаётся задачей кода самого сервиса (повторные попытки подключения), а не базового рантайма контейнеров.

Локальная лабораторная / самопроверка
Что уже дано
Работайте в POSIX-терминале с Linux-контейнерами. Сохраните выданные файлы в новый каталог l26_lab, который ещё не существует; если занят, выберите другой. Выберите уникальный префикс, например LAB=cf_docker_task_l26_run01; замените run01 при повторе. Перед созданием проверьте свои предполагаемые имена через docker container ls -a, network inspect и volume inspect, если они используются. Занятый объект не удаляйте: выберите другой префикс. Ошибку связи с Engine не принимайте за отсутствие ресурса. Порты хоста должны быть свободны; PORTS не исключает отдельный процесс ОС. При неожиданном bind-конфликте остановите опыт и согласованно замените host-порт/URL, не останавливая прежние службы. Все новые имена контейнеров/сетей/томов и проект Compose начинаются с выбранного LAB. Готовые app.py/SQL менять или писать не требуется. Соберите выданный Dockerfile из этого каталога в свой образ ${LAB}:app как подготовку; рецепт уже известен, кроме явно дефектного файла текущего задания.
Дан полный намеренно неисправный compose.yaml: db healthcheck опрашивает5433 вместо5432, app ожидает service_healthy. PostgreSQL17.4, новый db_data, task-demo-26, HTTP18226. Приложение инициализируется supplied init; app healthcheck, если будет добавлен во втором опыте, не должен ждать схему до init.
Что нужно сделать
Сначала на отдельном запуске того же нового проекта замените depends_on у app на краткое [db] и добавьте db готовую command: ["sh","-c","sleep 8; exec docker-entrypoint.sh postgres"]. sleep задерживает штатный старт БД на8секунд, exec заменяет оболочку штатным entrypoint. Пока TCPpg_isready возвращает2, получите /health200 и /ready503 работающего app. После pg_isready0 до первого init /ready по-прежнему503: это уже отсутствие схемы. Затем выполните init, подтвердите /ready200, выполните down без-v и восстановите исходную длинную зависимость/ошибочный healthcheck, удалив задержку. Получите реальное состояние db и зависимого app при проваленной проверке. Исправьте проверку, дождитесь старта app, выполните init и проверьте готовность. Затем в своей копии задайте для app контрольную заведомо неуспешную проверку test: ["CMD","python","app.py","fail"] с коротким interval/retries. Команда fail завершает только проверочный процесс с7; основной serve остаётся. Сопоставьте Running, Health, Container ID и RestartCount до/после unhealthy. В конце отдельно остановите db и сравните /health с /ready app.
Ввод и вывод
Вы самостоятельно вводите Docker/Compose-команды на хосте и сохраняете их реальные результаты в локальный текстовый отчёт. Печатать кодовое слово, писать Python/SQL или заполнять форму ответа не требуется. Эта TEXT-лабораторная не имеет автоматической платформенной проверки.
Проверка результата:
- Краткая зависимость допускает работающий app при TCPpg_isready2; после TCP0, но до init, readiness всё ещё503. После init200: причины различены, ранний старт не объявлен обязательным падением app.
- В начале db работает, но healthcheck не проходит; app может быть Created, однако его основной процесс не запущен до успешной проверки зависимости.
- После ремонта init0 и /ready200. При failing app-health основной процесс остаётсяRunning с тем жеID и RestartCount, хотя Health=unhealthy. После stopdb /health200 и /ready503 — это другой опыт, не доказательство health-рестарта.
Сохраните команды, фактические ID/коды и наблюдения до очистки. После опыта штатно остановите и удалите только контейнеры своего LAB; для Compose используйте то же -p и down без-v, затем удалите только свою свободную сеть. Тома с контрольными записями сохраняйте, кроме отдельно оговорённого разрушительного сравнения урока27. Если позже удаляете собственный одноразовый том, сначала назовите точное имя и потерю его данных; чужие ресурсы и общие образы сохраняйте.
Ограничения:
- Локальная самопроверка; платформа не запускает весь Docker-проект, не оценивает отчёт автоматически и не подтверждает выполнение ученика.
- Никакого prune, privileged, Docker socket или реальных секретов. Только собственные новые учебные ресурсы.
- Эталон/ошибочные варианты проверяются отдельно на локальном Engine29.6.2; это не сертификация Latorn Docker27 или всех ОС.
Файл app.py:
import json
import os
from pathlib import Path
import signal
import sys
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.request import urlopen
META = json.loads(Path(__file__).with_name("app_metadata.json").read_text())
VERSION = Path(__file__).with_name("version.txt").read_text().strip()
MODE = os.getenv("STORAGE", "memory")
LOCK = threading.RLock()
MEMORY = []
def log(event, **fields):
print(json.dumps({"event": event, **fields}), flush=True)
def password():
direct, filename = os.getenv("PASSWORD", ""), os.getenv("PASSWORD_FILE", "")
if direct and filename:
raise ValueError("PASSWORD and PASSWORD_FILE are mutually exclusive")
value = Path(filename).read_text().rstrip("\r\n") if filename else direct
if not value:
raise ValueError("postgres mode requires PASSWORD or PASSWORD_FILE")
return value
def connect():
import psycopg
return psycopg.connect(host=os.getenv("DB_HOST", "db"), port=int(os.getenv("DB_PORT", "5432")),
dbname=os.getenv("DB_NAME", "course"), user=os.getenv("DB_USER", "course"),
password=password(), connect_timeout=2, options="-c statement_timeout=3000")
def file_path():
return Path(os.getenv("DATA_FILE", "/data/items.json"))
def rows():
with LOCK:
if MODE == "memory":
return list(MEMORY)
if MODE == "file":
p = file_path()
return json.loads(p.read_text()) if p.exists() else []
with connect() as db:
records = db.execute("SELECT id, text FROM course_items ORDER BY id").fetchall()
return [{"id": r[0], "text": r[1]} for r in records]
def add(text):
with LOCK:
if MODE == "postgres":
with connect() as db:
row = db.execute("INSERT INTO course_items (text) VALUES (%s) RETURNING id, text", (text,)).fetchone()
return {"id": row[0], "text": row[1]}
current = rows()
item = {"id": max([r["id"] for r in current], default=0) + 1, "text": text}
current.append(item)
if MODE == "memory":
MEMORY[:] = current
else:
p = file_path()
temp = p.with_suffix(".tmp")
temp.write_text(json.dumps(current), encoding="utf-8")
temp.replace(p)
return item
def ready():
if MODE == "postgres":
with connect() as db:
found = db.execute("SELECT version FROM course_schema WHERE singleton = TRUE").fetchone()
if found != (1,):
raise RuntimeError("schema 1 required; run supplied init command")
db.execute("SELECT id FROM course_items LIMIT 1")
elif MODE == "file":
p = file_path()
if not p.parent.is_dir() or not os.access(p.parent, os.W_OK):
raise RuntimeError("DATA_FILE parent must exist and be writable")
rows()
elif MODE != "memory":
raise ValueError("STORAGE must be memory, file or postgres")
def init():
if MODE == "postgres":
with connect() as db:
db.execute("CREATE TABLE IF NOT EXISTS course_schema (singleton boolean PRIMARY KEY CHECK (singleton), version integer NOT NULL)")
db.execute("INSERT INTO course_schema VALUES (TRUE, 1) ON CONFLICT (singleton) DO NOTHING")
version = db.execute("SELECT version FROM course_schema").fetchone()
if version != (1,):
raise RuntimeError("refusing incompatible schema")
db.execute("CREATE TABLE IF NOT EXISTS course_items (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, text varchar(200) NOT NULL)")
elif MODE == "file":
p = file_path()
if not p.exists():
p.write_text("[]", encoding="utf-8")
ready()
print(json.dumps({"status": "initialized", "schema": 1}))
class Handler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
log("request", method=self.command, path=self.path, detail=fmt % args)
def send(self, code, data):
body = json.dumps(data, ensure_ascii=False).encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
try:
if self.path == "/":
filename = os.getenv("MESSAGE_FILE")
message = Path(filename).read_text().strip() if filename else os.getenv("MESSAGE", "hello from Docker")
self.send(200, {"service": META["service"], "message": message, "storage": MODE})
elif self.path == "/health":
self.send(200, {"status": "alive"})
elif self.path == "/ready":
ready()
self.send(200, {"status": "ready"})
elif self.path == "/version":
self.send(200, {"service": META["service"], "version": VERSION, "schema": 1})
elif self.path == META["collection"]:
ready()
self.send(200, {"items": rows()})
elif self.path == "/upstream":
url = os.environ["DEPENDENCY_URL"]
with urlopen(url, timeout=2) as reply:
self.send(200, {"dependency": json.load(reply)})
else:
self.send(404, {"error": "not_found"})
except Exception as exc:
log("dependency_error", kind=type(exc).__name__)
self.send(503, {"status": "not_ready"} if self.path == "/ready" else {"error": "storage_or_dependency_unavailable"})
def do_POST(self):
if self.path != META["collection"]:
self.send(404, {"error": "not_found"})
return
try:
length = int(self.headers.get("Content-Length", "0"))
if not 0 < length <= 4096:
raise ValueError("body size")
data = json.loads(self.rfile.read(length))
text = data.get("text") if isinstance(data, dict) else None
if not isinstance(text, str) or not 1 <= len(text.strip()) <= 200:
raise ValueError("text must have 1..200 characters")
except (ValueError, TypeError):
self.send(400, {"error": "invalid_text"})
return
try:
ready()
self.send(201, add(text.strip()))
except Exception as exc:
log("storage_error", kind=type(exc).__name__)
self.send(503, {"error": "storage_unavailable"})
def serve():
if MODE not in ("memory", "file", "postgres"):
raise ValueError("invalid STORAGE")
if MODE == "postgres":
password() # configuration errors fail immediately; DB outages do not kill HTTP
Path("/tmp/course-app.pid").write_text(str(os.getpid()))
server = ThreadingHTTPServer((os.getenv("HOST", "0.0.0.0"), int(os.getenv("PORT", "8000"))), Handler)
server.daemon_threads = True
stopping = threading.Event()
def stop(signum, _frame):
if not stopping.is_set():
stopping.set()
log("signal_received", signal=signal.Signals(signum).name)
threading.Thread(target=server.shutdown, daemon=True).start()
signal.signal(signal.SIGTERM, stop)
signal.signal(signal.SIGINT, stop)
log("started", service=META["service"], version=VERSION, storage=MODE, uid=os.getuid())
server.serve_forever(poll_interval=0.1)
server.server_close()
log("stopped")
def main():
command = sys.argv[1] if len(sys.argv) > 1 else "serve"
if command == "serve":
serve()
elif command == "init":
init()
elif command == "seed":
ready()
for text in META["seed"]:
if not any(row["text"] == text for row in rows()):
add(text)
print(json.dumps({"items": rows()}))
elif command == "inspect":
ready()
print(json.dumps({"items": rows()}))
elif command == "check":
ready()
print(json.dumps({"status": "ready", "schema": 1}))
elif command == "http-check":
with urlopen("http://127.0.0.1:" + os.getenv("PORT", "8000") + "/ready", timeout=2) as r:
print(r.read().decode())
elif command == "fail":
log("intentional_failure")
sys.exit(7)
elif command == "allocate":
# Use ONLY with a bounded container memory limit; held blocks force observable allocation.
blocks = []
while True:
blocks.append(bytearray(1024 * 1024))
else:
raise ValueError("unknown supplied command")
if __name__ == "__main__":
try:
main()
except Exception as exc:
log("fatal", kind=type(exc).__name__)
sys.exit(1)
Файл requirements.txt:
psycopg==3.2.6 psycopg-binary==3.2.6 typing-extensions==4.12.2
Файл app_metadata.json:
{
"service": "course-notes",
"collection": "/notes",
"seed": [
"first supplied note",
"data survives container recreation"
]
}
Файл version.txt:
1.0.0
Файл Dockerfile:
FROM python:3.12.10-slim-bookworm AS dependencies
WORKDIR /build
COPY requirements.txt .
RUN python -m pip install --no-cache-dir --prefix=/install -r requirements.txt && printf 'build stage only\n' > /build/build-only.txt
FROM python:3.12.10-slim-bookworm AS runtime
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
COPY --from=dependencies /install /usr/local
COPY app.py app_metadata.json ./
ARG APP_VERSION=1.0.0
COPY versions/${APP_VERSION}.txt ./version.txt
RUN groupadd --gid 10001 app && useradd --uid 10001 --gid 10001 --no-create-home app && mkdir /data && chown 10001:10001 /data
USER 10001:10001
EXPOSE 8000
STOPSIGNAL SIGTERM
CMD ["python", "-u", "app.py", "serve"]
Файл versions/1.0.0.txt:
1.0.0
Файл compose.yaml:
{
"services": {
"db": {
"image": "postgres:17.4-bookworm",
"environment": {
"POSTGRES_DB": "course",
"POSTGRES_USER": "course",
"POSTGRES_PASSWORD": "task-demo-26",
"PGDATA": "/var/lib/postgresql/data/pgdata"
},
"volumes": [
"db_data:/var/lib/postgresql/data"
],
"healthcheck": {
"test": [
"CMD",
"pg_isready",
"-h",
"127.0.0.1",
"-p",
"5433",
"-U",
"course",
"-d",
"course"
],
"interval": "1s",
"timeout": "2s",
"retries": 3,
"start_period": "1s"
}
},
"app": {
"image": "cf_docker_task_l26_run01:app",
"ports": [
"127.0.0.1:18226:8000"
],
"environment": {
"STORAGE": "postgres",
"DB_HOST": "db",
"DB_PORT": "5432",
"DB_NAME": "course",
"DB_USER": "course",
"PASSWORD": "task-demo-26"
},
"depends_on": {
"db": {
"condition": "service_healthy"
}
}
}
},
"volumes": {
"db_data": null
}
}
Лабораторная выполняется локально. Latorn не запускает Docker Compose или весь проект и не подтверждает выполнение лабораторной автоматически.
Задача 1
У app указано depends_on: [db]. Процесс PostgreSQL уже запущен, но начальная инициализация ещё не закончилась. Что гарантирует эта краткая форма зависимости при запуске Compose?
Задача 2
App уже работает после старта с depends_on db: condition: service_healthy. Позже db получила unhealthy; процесс db продолжает работать. Других оркестраторов и механизмов реакции нет. Что произойдёт с app из-за одного этого изменения health?
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
