Docker Compose на примере Nginx + PHP + MySQL

02 September 2024

Docker Compose: быстрый способ развернуть стек Nginx + PHP + MySQL.

Docker Compose — это инструмент, который позволяет описывать и запускать целые проекты с несколькими контейнерами через один YAML-файл. В этой статье разберём два способа развёртывания простого веб-стека:

  • Через обычный shell-скрипт с использованием docker run
  • Через docker-compose.yaml — более удобный и современный способ

1. Проект через Shell-Script

Начнём с классического подхода: создадим необходимые директории, сконфигурируем Nginx, соберём PHP-FPM и запустим все контейнеры вручную.

#!/bin/bash
mkdir -p mysql-data www images/php && docker network create --driver bridge dockernet1

# Конфиг для Nginx (ручной режим)
echo 'server {
    listen 80;
    root /var/www/public_html;
    index  index.php index.html index.htm;
    server_name localhost devopz.tech www.devopz.tech;
    location ~ \.php$ {
        fastcgi_pass fpm71:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}' > www/nginx.conf

# Конфиг для Nginx (вариант для Compose)
echo 'server {
    listen 80;
    root /var/www/public_html;
    index  index.php index.html index.htm;
    server_name localhost devopz.tech www.devopz.tech;
    location ~ \.php$ {
        fastcgi_pass compose-fpm71:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}' > www/compos.cnf

# Простой PHP-скрипт для проверки подключения к MySQL
echo "<?php
$user = 'root';
$pass = 'root';
$dsn = 'mysql:host=mysql1;dbname=information_schema;charset=utf8';
$pdo = new PDO(\$dsn, \$user, \$pass);
\$stmt = \$pdo->query('select * from tables');
\$row = \$stmt->fetch();
print_r(\$row);" > www/index.php

# Dockerfile для PHP-FPM
mkdir -p images/fpm71
echo 'FROM php:7.1-fpm
RUN apt update && apt install -y curl git && docker-php-ext-install pdo pdo_mysql
WORKDIR /var/www/public_html
ENTRYPOINT ["php-fpm"]' > images/fpm71/Dockerfile

# Сборка и запуск контейнеров вручную
docker build -t php71fpm images/fpm71
docker run -d          -v ./www:/var/www/public_html                                                    --network dockernet1 --name fpm71 php71fpm
docker run -d -p 80:80 -v ./www/nginx.conf:/etc/nginx/conf.d/default.conf -v ./www:/var/www/public_html --network dockernet1 --name nginx1 nginx
docker run -d          -v ./mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root                       --network dockernet1 --name mysql1 mysql
docker run -d -p 81:80                                -e PMA_HOST=mysql1                                --network dockernet1 --name phpmyadmin1 phpmyadmin

sleep 10 && docker ps -a

После выполнения скрипта вы получите рабочий стек:

  • Nginxhttp://localhost:80
  • PHP-FPM — собран из кастомного Dockerfile
  • MySQL — с паролем root/root
  • phpMyAdmin — доступен на порту 81

2. Проект через docker-compose.yaml

Теперь сделаем то же самое, но в одном YAML-файле. Это гораздо удобнее и поддерживается командой docker compose up -d.

networks:
  dockernet:
    name: dockernet

services:
  nginx:
    container_name: compose-nginx
    image: nginx:latest
    ports:
      - 80:80
    volumes:
      - ./www/compos.cnf:/etc/nginx/conf.d/default.conf
      - ./www:/var/www/public_html
    networks:
      - dockernet
    depends_on:
      - php
      - mysql

  php:
    container_name: compose-fpm71
    build: ./images/fpm71
    volumes:
      - ./www:/var/www/public_html
    networks:
      - dockernet

  mysql:
    container_name: compose-mysql
    image: mysql:latest
    volumes:
      - ./mysql-data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=root
    networks:
      - dockernet

  phpmyadmin:
    container_name: compose-admin
    image: phpmyadmin:latest
    ports:
      - 82:80
    environment:
      - PMA_HOST=compose-mysql
    networks:
      - dockernet

Запуск всего стека:

docker compose up -d

Теперь вы можете изменять конфиги и код без пересборки контейнеров — docker-compose сам всё подхватит при рестарте.


3. Dockerfile — основные инструкции

Dockerfile — это рецепт сборки образа. Вот краткий список ключевых команд:

  1. FROM — базовый образ.
  2. LABEL — метаданные образа (автор, версия и т.д.).
  3. ENV — переменные среды.
  4. RUN — выполнение команд при сборке.
  5. COPY — копирование файлов в образ.
  6. ADD — как COPY, но умеет распаковывать архивы.
  7. CMD — команда, выполняемая при запуске контейнера.
  8. WORKDIR — рабочая директория для следующих команд.
  9. ARG — переменные сборки.
  10. ENTRYPOINT — основная команда контейнера (не переопределяется).
  11. EXPOSE — объявление портов.
  12. VOLUME — точки монтирования.
# Пример: создание Dockerfile и переход в директорию
mkdir dockerproject && cd dockerproject && nano Dockerfile

4. Docker Compose — Чеклист команд

Ниже — короткая шпаргалка по самым нужным командам Docker Compose:

# Запуск всех сервисов в фоне
docker compose up -d

# Остановка всех контейнеров проекта
docker compose down

# Просмотр логов всех сервисов
docker compose logs -f

# Пересборка образов и перезапуск контейнеров
docker compose up -d --build

# Проверка списка работающих контейнеров
docker compose ps

# Просмотр списка сервисов и их состояния
docker compose top

# Выполнить команду внутри контейнера
docker compose exec SERVICE_NAME bash

# Проверка конфигурации файла docker-compose.yaml
docker compose config

Вывод

Shell-скрипт подходит для быстрых экспериментов и ручного управления контейнерами.

Docker Compose удобен для реальных проектов: легко масштабируется, читается и разворачивается одной командой.


5. Volumes — постоянные данные вне контейнера

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

Виды Volume

Тип Пример Особенности
Анонимный VOLUME /var/lib/mysql Docker сам создаёт volume без имени. Живёт, пока его не очистят вручную.
Именованный volumes: - dbdata:/var/lib/mysql Можно использовать повторно в разных контейнерах. Управляется через Docker CLI.
Bind Mount volumes: - ./www:/var/www/html Монтирует локальную папку хоста. Удобно для разработки, но требует аккуратности в проде.

Пример в docker-compose.yaml

volumes:
  dbdata:

services:
  mysql:
    image: mysql:8
    container_name: mysql-prod
    environment:
      - MYSQL_ROOT_PASSWORD=supersecret
    volumes:
      - dbdata:/var/lib/mysql

  app:
    build: ./images/php
    volumes:
      - ./src:/var/www/html   # bind mount для кода
    depends_on:
      - mysql

Советы по Volume

  • Для баз данных и конфигов — используй именованные volume.
  • Для локальной разработки — bind mount удобен, но в продакшене лучше собирать код в образ.
  • Очищай неиспользуемые volume: docker volume prune

6. Secrets — безопасное хранение паролей и токенов

Жёстко прописывать пароли в docker-compose.yaml ❌ — плохая идея. Есть несколько способов передавать секреты безопасно:

.env файл

# .env
MYSQL_ROOT_PASSWORD=supersecret
PMA_PASSWORD=adminpass
# docker-compose.yaml
services:
  mysql:
    image: mysql:8
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}

.env не коммитим в Git (добавляем в .gitignore).


Docker Secrets (Swarm / Compose v3.5+)

# Создание секрета
echo "supersecret" | docker secret create mysql_root_password -
# docker-compose.yaml
version: '3.8'

services:
  mysql:
    image: mysql:8
    secrets:
      - mysql_root_password

secrets:
  mysql_root_password:
    external: true

Секрет монтируется в контейнер как файл в `/run/secrets/...`, а не как переменная среды ✅


HashiCorp Vault или внешние Secret Manager

Для продакшена на k8s или сложных проектах удобно подключать HashiCorp Vault, AWS Secrets Manager, Google Secret Manager и т.д. Vault позволяет ротацию ключей, политки доступа и логирование, а не просто хранение паролей в файлах.

# Пример (через Vault CLI)
vault kv put secret/mysql root_password=supersecret

Далее контейнеры могут получать секреты через entrypoint-скрипты, sidecar или init-контейнеры.


7. Best Practices для продакшена

  • Multi-stage builds — собирай код и зависимости в одном этапе, а финальный образ делай максимально лёгким.
# Stage 1 — Build
FROM composer:2 as builder
WORKDIR /app
COPY . .
RUN composer install --no-dev --optimize-autoloader

# Stage 2 — Runtime
FROM php:8.2-fpm
WORKDIR /var/www/html
COPY --from=builder /app ./
  • Минимизируй количество слоёв в Dockerfile — объединяй RUN команды.
  • Никогда не храни пароли в git, даже в приватных репозиториях.
  • Не запускай процессы от root внутри контейнера — используй USER.
  • Храни конфиги отдельно от кода (12-Factor App), используй переменные окружения или volume.
  • Чисти временные файлы и кэш в RUN инструкциях, чтобы не раздувать образ.
  • Мониторь контейнеры: логирование (Filebeat, Loki), метрики (Prometheus), алерты.
  • Регулярно обновляй образы базовых контейнеров и зависимости.
  • Версионируй образы (tag'и), не используй :latest в продакшене.

Вывод

Volumes обеспечивают постоянство данных. Secrets — защищённое хранение конфиденциальных параметров. Best practices делают твой стек безопасным, предсказуемым и оптимальным для CI/CD и продакшена.

Следующий шаг — оркестрация через Kubernetes или Docker Swarm

Leave a comment

Popular Posts

Advertisement

Headlines