Docker за PHP в производство: практическо ръководство за настройка на бизнес приложения
Docker кара PHP приложение да работи по един и същи начин на лаптоп, при тестване и в производство, и прави изданията и връщанията рутинни, вместо стресиращи. Той също така добавя движещи се части и небрежната настройка е по-лоша от обикновен сървър. Това ръководство показва готова за производство базова линия с реална конфигурация и кога не трябва да се притеснявате.
Накратко
- Docker си струва, когато искате идентични среди, бързи връщания назад и лесно включване. За един малък сайт с брошури на споделен хостинг това обикновено не е необходимо.
- Създайте неизменно изображение в CI: многоетапно изграждане, само производствени зависимости, не-root потребител, OPcache включен, грешки регистрирани и никога не показвани.
- Освобождаване чрез таг на изображението (хешът на Git commit), проверка на здравето след стартиране и автоматично връщане към предишния таг, ако проверката е неуспешна.
- Контейнерите са за еднократна употреба, данните не са: съхранявайте базата данни и качванията в томове или управлявани услуги и ги архивирайте извън Docker.
- Най-често срещаните производствени грешки: тайни в изображението, публикувани портове на база данни, неограничени регистрационни файлове и ограничения на php-fpm, които не отговарят на паметта, която имате.
За много бизнес приложения реалната цена на хостинг не е сървърът, а хората. Настройка, която само един разработчик разбира, внедряване, извършено ръчно през FTP, и промеждутъчна среда, която се държи различно от производствената, са скъпи в дългосрочен план. Контейнерите се справят с това, като опаковат приложението с неговата точна PHP версия и разширения. Ние управляваме нашите собствени PHP сайтове и клиентски системи на стек, базиран на Docker, и това ръководство описва базовата линия, от която започваме.
Кога Docker си заслужава и кога не
- Заслужава си: няколко услуги (уеб, PHP, база данни, опашка), повече от един разработчик, необходимост от повтарящи се издания и бързи връщания назад или план за преместване между доставчици.
- Не е необходимо: прост сайт със съдържание, който рядко се променя на управляван хостинг. Добавената сложност изисква отплата.
- Не е заместител на добрите операции: все още се нуждаете от корекции, архивиране и наблюдение. Вижте нашето ръководство за архивиране и наблюдение.
Целевата архитектура
Солидна, проста настройка за PHP приложение на един сървър:
- Обратно прокси и TLS на ръба ( Nginx или Caddy на хоста или CDN като Cloudflare, вижте нашето ръководство за настройка на Cloudflare).
- Уеб контейнер ( Nginx), който обслужва статични файлове и предава PHP заявки.
- PHP-FPM контейнер с вашия код, вграден в изображението.
- База данни в собствен контейнер с постоянен обем или по-добре управлявана услуга за база данни за критични данни.
- Томове само за данни, които трябва да оцелеят: файлове на бази данни и потребителски качвания.
Многоетапен Dockerfile
Многоетапното компилиране инсталира зависимости на един етап и копира само резултата в тънко изображение за изпълнение. Крайното изображение няма Composer, компилатори или инструменти за тестване, което го прави по-малък и дава на нападателите по-малко за използване:
# ---- stage 1: PHP dependencies ----
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --no-interaction --optimize-autoloader
# ---- stage 2: runtime ----
FROM php:8.3-fpm-alpine
RUN apk add --no-cache icu-libs libzip \
&& apk add --no-cache --virtual .build icu-dev libzip-dev $PHPIZE_DEPS \
&& docker-php-ext-install -j"$(nproc)" pdo_mysql intl zip opcache \
&& apk del .build
COPY docker/php.ini /usr/local/etc/php/conf.d/zz-app.ini
COPY docker/www.conf /usr/local/etc/php-fpm.d/zz-www.conf
WORKDIR /var/www/app
COPY --from=vendor /app/vendor ./vendor
COPY --chown=www-data:www-data . .
USER www-data
EXPOSE 9000
Изберете PHP версията съзнателно и проверете дали все още се поддържа. Нашето ръководство за надграждане на PHP 8 обяснява как да планирате това. Закачете версии на базови изображения и ги актуализирайте по график, тъй като изображение, което никога не възстановявате, натрупва известни уязвимости. Запазете .dockerignore, който изключва .git, .env, тестове и локални данни, така че нищо чувствително да не попадне в изображението.
Производствен PHP и php-fpm конфигурация
# docker/php.ini
expose_php = 0
display_errors = 0
log_errors = 1
memory_limit = 256M
upload_max_filesize = 10M
post_max_size = 12M
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0 ; the code in an image never changes while it runs
# docker/www.conf
[www]
pm = dynamic
pm.max_children = 12
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
request_terminate_timeout = 30s
catch_workers_output = yes
decorate_workers_output = no
Задаването на opcache.validate_timestamps = 0 дава най-добра производителност за неизменно изображение, но работи само ако никога не редактирате файлове в работещ контейнер: вместо това пускате ново изображение. Оразмерете пула от реални числа: измерете средната памет на един PHP работник под натоварване, решете колко памет PHP може да използва на сървъра и разделете. Ако работният файл използва 60 MB и можете да дадете на PHP 1 GB, pm.max_children е около 16. Задаването му на много по-високо ниво просто прави размяната на сървъра или процесите на ядрото убиват по време на пик на трафика. Твърде ниско и опашка от заявки. Проверете дневника на php-fpm за предупреждения „max_children достиган“.
Съставен файл с правилните настройки по подразбиране
x-logging: &default-logging
driver: json-file
options: { max-size: "10m", max-file: "5" } # logs cannot fill the disk
services:
web:
image: ghcr.io/your-org/app-web:${TAG}
ports: ["127.0.0.1:8080:80"] # only the local reverse proxy can reach it
depends_on: [php]
restart: unless-stopped
logging: *default-logging
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/health.php"]
interval: 15s
timeout: 3s
retries: 3
php:
image: ghcr.io/your-org/app:${TAG}
env_file: .env # secrets stay on the server, not in the image
restart: unless-stopped
read_only: true
tmpfs: [/tmp]
mem_limit: 512m
volumes: [uploads:/var/www/app/storage/uploads]
logging: *default-logging
db:
image: mysql:8.4
env_file: .env.db
restart: unless-stopped
volumes: [db_data:/var/lib/mysql] # no "ports:" - the database is not public
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
interval: 15s
retries: 5
logging: *default-logging
volumes:
uploads:
db_data:
web изображението е малко изображение Nginx с копирана вашата public/ папка и конфигурация на сайта, вградено в същия конвейер като PHP изображението. Ключови точки: обвържете публикуваните портове с 127.0.0.1, никога не публикувайте базата данни, задайте правила за рестартиране, ограничете паметта, така че един контейнер да не може да свали хоста, и ограничете размера на журнала. Не забравяйте, че портовете, публикувани от Docker могат да заобиколят правилата на защитната стена на хоста, както обясняваме в ръководството Cloudflare. Никога не монтирайте Docker сокета в контейнер на приложение, защото той дава на този контейнер контрол над хоста.
Освобождавания и връщания назад
Най-добрата характеристика на настройката, базирана на изображение, е, че всяко издание е етикет, а връщането назад е „стартиране на предишния етикет“. Маркирайте изображения с хеша на Git commit, никога не разчитайте на latest и накарайте скрипта за освобождаване да проверява изправността:
#!/usr/bin/env bash
# /srv/app/release.sh <git-sha>
set -euo pipefail
TAG="${1:?usage: release.sh <git-sha>}"
cd /srv/app
PREV="$(cat .current_tag 2>/dev/null || true)"
export TAG
docker compose pull php web
docker compose up -d --no-deps php web
for i in $(seq 1 30); do
if curl -fsS http://127.0.0.1:8080/health.php > /dev/null; then
echo "$TAG" > .current_tag
echo "healthy: $TAG"; exit 0
fi
sleep 2
done
echo "unhealthy after release, rolling back to ${PREV:-none}" >&2
if [ -n "$PREV" ]; then TAG="$PREV" docker compose up -d --no-deps php web; fi
exit 1
Промените в базата данни се нуждаят от внимание: напишете миграции, така че предишната версия на кода все още да работи с новата схема (първо добавете колони, премахнете ги в по-късна версия). Това прави бързото връщане назад безопасно. Направете резервно копие на база данни преди всяка версия, която променя схемата.
Изграждане и доставка от CI
Изградете изображението веднъж в CI и разположете точно този артефакт. Минимален работен процес GitHub Actions:
name: build-and-release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/your-org/app:${{ github.sha }}
- name: Release over SSH
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
install -m 600 /dev/null key && echo "$DEPLOY_KEY" > key
ssh -i key -o StrictHostKeyChecking=accept-new deploy@your-server "/srv/app/release.sh ${{ github.sha }}"
Изпълнете вашите тестове и статичен анализ в същия конвейер, преди изображението да бъде изградено, и използвайте ключ за внедряване, който може да изпълнява само скрипта за освобождаване. Добавете сканиране за уязвимост на изображение, ако вашият рисков профил го изисква.
Дневници, данни и сигурност
- Регистрационните файлове отиват в stdout и stderr, ротират се от Docker и за нещо важно се изпращат на централно място, така че да оцелеят в контейнера.
- Данните живеят извън контейнера. Томът не е резервно копие. Използвайте подхода в нашето ръководство за архивиране: криптиран, извън сайта, тестван за възстановяване.
- Тайните никога не се изпичат в образи. Прекарайте ги по време на изпълнение през env файлове или мениджър на тайни и ги пазете от Git.
- Стартирайте като не-root потребител, използвайте файлова система само за четене, където е възможно, и изтрийте това, което не ви трябва.
- Поддържайте свежи изображения. Преизграждайте редовно, за да вземете поправки за сигурност в базовото изображение и PHP.
- Приложете контролите на ниво приложение от нашия контролен списък за сигурност PHP, тъй като контейнерите не поправят несигурен код.
Контролен списък за готовност за производство
- Многоетапно изображение само с производствени зависимости, фиксирано основно изображение, не-root потребител.
- OPcache активиран, регистрираните грешки не се показват, php-fpm е оразмерен от измерена памет.
- Създайте файл с правила за рестартиране, проверки на състоянието, ограничения на паметта, ротация на регистрационни файлове и без порт на публична база данни.
- Издава чрез commit-hash таг с автоматична проверка на състоянието и връщане назад.
- Архивиране и тествано възстановяване за базата данни и качвания.
- Мониторинг и предупреждения за здраве, диск, грешки и сертификати.
Как можем да помогнем
Ние контейнеризираме PHP приложения, настройваме CI канали и версии и ги свързваме с плащания, CRM и съобщения. Вижте нашата услуга за интегриране или резервирайте обаждане и ние ще прегледаме текущия ви процес на хостинг и освобождаване.
Често задавани въпроси
Добър ли е Docker за стартиране PHP в производство?
Да, когато се използва добре. Предоставя идентични среди, повтарящи се издания и бързи връщания назад. Нуждае се от правилна конфигурация: неизменни изображения, проверки на състоянието, ограничения на ресурсите, ротация на регистрационни файлове, управление на тайни и архивиране на данни извън контейнера.
Трябва ли базата данни да работи в Docker в производството?
Може за малки и средни системи, ако използва постоянен том, никога не се излага публично и има тествани резервни копия. За критични данни много екипи предпочитат услуга за управлявана база данни, която обработва отказ, актуализации и архивиране вместо вас.
Как да върна версия на Docker ?
Маркирайте всяко изображение с хеша на Git commit и запазете предишния етикет. За да се върнете назад, стартирайте контейнерите с предишния етикет. Напишете миграции на бази данни, така че по-старият код все още да работи с по-новата схема.
Колко php-fpm работници трябва да конфигурирам?
Измерете средната памет на един работник под натоварване, решете колко памет PHP може да използва и разделете. Задаването на твърде висока стойност причинява размяна, докато твърде ниската причинява заявки на опашка. Гледайте регистъра на php-fpm за предупреждения за max_children.
Коя е най-голямата грешка в сигурността Docker с PHP приложения?
Поставяне на тайни в изображението или Git и публикуване на портове на база данни в интернет. Също така избягвайте да работите като root, да монтирате Docker сокета в контейнери и никога да не актуализирате базовото изображение.
Безплатно обаждане за откриване
Донесете разхвърлян стек или блокирала интеграция - Integrono ще набележи най-умния първи ход.
Получавате експертна оценка на бюджетни капани, зависимости и дали да купите, слепите или изградите - преди да започне платената работа. CEO остава в контура.