Практика 7. Docker Compose: многоконтейнерные приложения
Цель работы
Освоить Docker Compose для управления многоконтейнерными приложениями: сервисы, сети, volumes, healthchecks, переменные окружения.
Часть 1. Первый docker-compose.yml
Задание 1.1. Установка и проверка
- Проверьте версию Docker Compose:
Если не установлен:
Окно терминала docker compose versionОкно терминала sudo apt install docker-compose-plugin
Задание 1.2. Один сервис
-
Создайте рабочий каталог:
Окно терминала mkdir -p ~/compose-lab && cd ~/compose-lab -
Создайте
docker-compose.yml:services:web:image: nginx:alpineports:- "8080:80"volumes:- ./html:/usr/share/nginx/html:ro -
Создайте HTML-файл:
Окно терминала mkdir htmlecho "<h1>Docker Compose работает!</h1>" > html/index.html -
Запустите:
Окно терминала docker compose up -ddocker compose pscurl http://localhost:8080 -
Просмотрите логи и остановите:
Окно терминала docker compose logsdocker compose down
Часть 2. Многосервисное приложение
Задание 2.1. Приложение + БД + кэш
Создайте проект со следующей структурой:
~/compose-lab/├── docker-compose.yml├── .env├── app/│ ├── Dockerfile│ ├── requirements.txt│ └── app.py└── html/Файл app/requirements.txt:
flask==3.0.0psycopg2-binary==2.9.9redis==5.0.1Файл app/app.py:
import osimport jsonfrom datetime import datetimefrom flask import Flask, jsonifyimport psycopg2import redis
app = Flask(__name__)
# Подключение к Redisredis_client = redis.Redis( host=os.environ.get('REDIS_HOST', 'redis'), port=int(os.environ.get('REDIS_PORT', 6379)), decode_responses=True)
def get_db_connection(): return psycopg2.connect(os.environ.get('DATABASE_URL'))
@app.route('/')def index(): # Увеличиваем счётчик посещений в Redis visits = redis_client.incr('visits') return jsonify({ "message": "Hello from Docker Compose!", "visits": visits, "timestamp": datetime.now().isoformat(), "hostname": os.uname().nodename })
@app.route('/db')def db_check(): try: conn = get_db_connection() cur = conn.cursor() cur.execute('SELECT version();') version = cur.fetchone()[0] cur.close() conn.close() return jsonify({"database": "connected", "version": version}) except Exception as e: return jsonify({"database": "error", "message": str(e)}), 500
@app.route('/health')def health(): return jsonify({"status": "healthy"})
if __name__ == '__main__': port = int(os.environ.get('PORT', 5000)) app.run(host='0.0.0.0', port=port, debug=True)Файл app/Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
RUN adduser --disabled-password --gecos "" appuserUSER appuser
EXPOSE 5000
CMD ["python", "app.py"]Файл .env:
POSTGRES_USER=appuserPOSTGRES_PASSWORD=appsecretPOSTGRES_DB=appdbAPP_PORT=8080Файл docker-compose.yml:
services: web: build: context: ./app dockerfile: Dockerfile ports: - "${APP_PORT:-8080}:5000" environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} REDIS_HOST: redis REDIS_PORT: 6379 depends_on: db: condition: service_healthy redis: condition: service_healthy restart: unless-stopped networks: - app-network
db: image: postgres:16-alpine environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - db-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 start_period: 30s restart: unless-stopped networks: - app-network
redis: image: redis:7-alpine command: ["redis-server", "--maxmemory", "64mb", "--maxmemory-policy", "allkeys-lru"] volumes: - redis-data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 3 restart: unless-stopped networks: - app-network
volumes: db-data: redis-data:
networks: app-network: driver: bridgeЗадание 2.2. Запуск и проверка
-
Соберите и запустите:
Окно терминала cd ~/compose-labdocker compose up -d --build -
Проверьте состояние сервисов:
Окно терминала docker compose psДождитесь, пока все сервисы станут
healthy. -
Протестируйте приложение:
Окно терминала # Главная страница (с счётчиком посещений)curl http://localhost:8080curl http://localhost:8080curl http://localhost:8080# Проверка БДcurl http://localhost:8080/db# Health checkcurl http://localhost:8080/health -
Просмотрите логи:
Окно терминала docker compose logs -f web # Логи только web-сервисаdocker compose logs -f # Логи всех сервисов
Часть 3. Управление и отладка
Задание 3.1. Выполнение команд внутри контейнеров
-
Подключитесь к PostgreSQL:
Окно терминала docker compose exec db psql -U appuser -d appdbВнутри psql:
\dtSELECT version();\q -
Проверьте Redis:
Окно терминала docker compose exec redis redis-cliВнутри redis-cli:
GET visitsKEYS *EXIT -
Откройте shell в контейнере web:
Окно терминала docker compose exec web shenv | grep -E "(DATABASE|REDIS)"exit
Задание 3.2. Перезапуск и пересборка
-
Пересоберите только один сервис:
Окно терминала docker compose build webdocker compose up -d web -
Перезапустите без пересборки:
Окно терминала docker compose restart web -
Пересоберите всё с нуля:
Окно терминала docker compose build --no-cachedocker compose up -d
Задание 3.3. Остановка и удаление
-
Остановите всё:
Окно терминала docker compose downДанные в volumes сохраняются.
-
Остановите и удалите volumes:
Окно терминала docker compose down -vДанные потеряны.
Часть 4. Переменные окружения
Задание 4.1. Приоритет переменных
-
Измените порт через переменную окружения shell:
Окно терминала APP_PORT=9090 docker compose up -dcurl http://localhost:9090docker compose down -
Измените порт в
.envна8888и запустите без shell-переменной:Окно терминала # Отредактируйте .env: APP_PORT=8888docker compose up -dcurl http://localhost:8888docker compose down -
Используйте значение по умолчанию из compose-файла (удалите
APP_PORTиз.env):Окно терминала docker compose up -dcurl http://localhost:8080 # Используется default из ${APP_PORT:-8080}docker compose down
Задание 4.2. env_file
-
Создайте файл
config/app.env:Окно терминала mkdir configcat > config/app.env << 'EOF'LOG_LEVEL=debugSECRET_KEY=my-secret-key-12345EOF -
Подключите его в compose-файле к сервису
web:web:env_file:- ./config/app.env -
Проверьте, что переменные доступны внутри контейнера:
Окно терминала docker compose up -d --builddocker compose exec web env | grep -E "(LOG_LEVEL|SECRET_KEY)"docker compose down
Часть 5. Масштабирование
Задание 5.1. Запуск нескольких экземпляров
-
Измените порт web-сервиса, чтобы Docker назначал случайные порты:
web:ports:- "5000" # Случайный порт на хосте -
Запустите 3 экземпляра:
Окно терминала docker compose up -d --scale web=3docker compose ps -
Узнайте назначенные порты:
Окно терминала docker compose port web 5000docker compose ps --format "table {{.Name}}\t{{.Ports}}" -
Проверьте каждый экземпляр (hostname будет разным):
Окно терминала # Получите порты и проверьте каждыйfor port in $(docker compose ps web --format '{{.Ports}}' | grep -oP '\d+(?=->5000)'); doecho "Port $port:"curl -s http://localhost:$port | python3 -m json.toolecho ""done -
Вернитесь к одному экземпляру:
Окно терминала docker compose up -d --scale web=1docker compose down
Часть 6. Реестры образов
Задание 6.1. Тегирование и публикация
-
Тегируйте образ для Docker Hub (замените
yourusername):Окно терминала docker compose build webdocker tag compose-lab-web:latest yourusername/compose-lab-web:1.0.0docker images | grep compose-lab -
(Необязательно) Войдите в Docker Hub и отправьте:
Окно терминала docker logindocker push yourusername/compose-lab-web:1.0.0
Мини-проект: полноценный стек с Nginx
Дополните проект reverse proxy на Nginx, чтобы получить архитектуру:
Клиент → Nginx (порт 80) → Web App (порт 5000) → PostgreSQL + Redis-
Создайте файл
nginx/nginx.conf:upstream web_app {server web:5000;}server {listen 80;location / {proxy_pass http://web_app;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;}location /health {proxy_pass http://web_app/health;}} -
Добавьте сервис nginx в
docker-compose.yml:nginx:image: nginx:alpineports:- "80:80"volumes:- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:rodepends_on:- webrestart: unless-stoppednetworks:- app-network -
Уберите проброс порта у web-сервиса (трафик идёт только через nginx).
-
Запустите и проверьте:
Окно терминала docker compose up -d --buildcurl http://localhostcurl http://localhost/healthdocker compose logs -f nginxdocker compose down
Контрольные вопросы
- Какую проблему решает Docker Compose по сравнению с
docker run? - Из каких секций верхнего уровня состоит
docker-compose.yml? - Чем
imageотличается отbuildв описании сервиса? - Как сервисы обращаются друг к другу внутри Compose? Почему работают DNS-имена?
- Чем bind mount отличается от named volume в контексте Compose?
- Опишите приоритет переменных окружения в Docker Compose.
- Зачем нужны healthchecks? Чем
service_healthyотличается отservice_started? - Что произойдёт при
docker compose down? А приdocker compose down -v? - Какие ограничения у масштабирования через
--scale? - Чем Docker Compose отличается от Kubernetes? Когда использовать каждый?