Docker dla PHP w produkcji: praktyczny przewodnik konfiguracji aplikacji biznesowych
Docker sprawia, że aplikacja PHP działa w ten sam sposób na laptopie, podczas testowania i produkcji, a wydawanie i wycofywanie zmian jest rutynowe, a nie stresujące. Dodaje także ruchome części, a nieostrożna konfiguracja jest gorsza niż zwykły serwer. W tym przewodniku przedstawiono wersję bazową gotową do produkcji z rzeczywistą konfiguracją i momentami, w których nie należy się tym przejmować.
W skrócie
- Docker jest tego wart, jeśli chcesz mieć identyczne środowiska, szybkie wycofywanie zmian i łatwe wdrażanie. W przypadku pojedynczej małej witryny z broszurami na hostingu współdzielonym jest to zwykle niepotrzebne.
- Zbuduj niezmienny obraz w CI: kompilacja wieloetapowa, tylko zależności produkcyjne, użytkownik inny niż root, włączona OPcache, rejestrowane błędy i nigdy nie wyświetlane.
- Zwolnij za pomocą tagu obrazu (hash zatwierdzenia Git), sprawdź stan po uruchomieniu i automatycznie przywróć poprzedni tag, jeśli sprawdzenie się nie powiedzie.
- Kontenery są jednorazowe, dane nie: przechowuj bazę danych i przesyłane dane w woluminach lub usługach zarządzanych i twórz ich kopie zapasowe poza Docker.
- Najczęstsze błędy produkcyjne: sekrety w obrazie, opublikowane porty bazy danych, nieograniczone logi i limity php-fpm, które nie odpowiadają posiadanej pamięci.
W przypadku wielu zastosowań biznesowych prawdziwym kosztem hostingu nie jest serwer, ale ludzie. Konfiguracja zrozumiała tylko dla jednego programisty, wdrożenie wykonywane ręcznie przez FTP i środowisko testowe, które zachowuje się inaczej niż produkcyjne, są na dłuższą metę kosztowne. Kontenery rozwiązują ten problem, pakując aplikację z jej dokładną wersją PHP i rozszerzeniami. Prowadzimy własne witryny PHP i systemy klienckie na stosie opartym na Dockerze, a ten przewodnik opisuje punkt odniesienia, od którego zaczynamy.
Kiedy Docker jest tego wart, a kiedy nie
- Warto: kilka usług (sieć, PHP, baza danych, kolejka), więcej niż jeden programista, potrzeba powtarzalnych wydań i szybkich wycofywania zmian lub plan przemieszczania się między dostawcami.
- Niekonieczne: prosta witryna z treścią, która rzadko zmienia się na hostingu zarządzanym. Dodatkowa złożoność wymaga zapłaty.
- Nie zastąpi dobrych operacji: nadal potrzebujesz łatania, tworzenia kopii zapasowych i monitorowania. Zobacz nasz przewodnik dotyczący tworzenia kopii zapasowych i monitorowania.
Docelowa architektura
Solidna, prosta konfiguracja aplikacji PHP na jednym serwerze:
- Odwrotne proxy i TLS na brzegu ( Nginx lub Caddy na hoście lub CDN, taki jak Cloudflare, zobacz nasz przewodnik konfiguracji Cloudflare).
- Kontener WWW ( Nginx), który obsługuje pliki statyczne i przekazuje dalej żądania PHP.
- Kontener PHP-FPM z Twoim kodem wbudowany w obraz.
- Baza danych we własnym kontenerze ze stałym wolumenem lub lepiej zarządzana usługa bazy danych dla krytycznych danych.
- Woluminy tylko dla danych, które muszą przetrwać: pliki baz danych i przesłane przez użytkowników.
Wieloetapowy plik Dockerfile
Kompilacja wieloetapowa instaluje zależności w jednym etapie i kopiuje tylko wynik do wąskiego obrazu środowiska wykonawczego. Ostateczny obraz nie zawiera Composer, kompilatorów ani narzędzi testowych, co czyni go mniejszym i daje atakującym mniej możliwości użycia:
# ---- 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
Wybierz wersję PHP celowo i sprawdź, czy jest nadal obsługiwana. Nasz przewodnik aktualizacji PHP 8 wyjaśnia, jak to zaplanować. Przypinaj wersje obrazów podstawowych i aktualizuj je zgodnie z harmonogramem, ponieważ obraz, którego nigdy nie odbudujesz, gromadzi znane luki w zabezpieczeniach. Zachowaj plik .dockerignore wykluczający .git, .env, testy i dane lokalne, aby na obrazie nie znalazło się nic wrażliwego.
Produkcja PHP i konfiguracja 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
Ustawienie opcache.validate_timestamps = 0 zapewnia najlepszą wydajność w przypadku niezmiennego obrazu, ale działa tylko wtedy, gdy nigdy nie edytujesz plików w działającym kontenerze: zamiast tego publikujesz nowy obraz. Rozmiar puli na podstawie liczb rzeczywistych: zmierz średnią pamięć jednego pracownika PHP pod obciążeniem, zdecyduj, ile pamięci PHP może używać na serwerze, i podziel. Jeśli pracownik używa 60 MB, a Ty możesz dać PHP 1 GB, wartość pm.max_children wynosi około 16. Ustawienie znacznie wyższej wartości powoduje jedynie zamianę serwera lub procesy zabijania jądra podczas wzrostu ruchu. Zbyt niska i kolejka żądań. Sprawdź dziennik php-fpm pod kątem ostrzeżeń „osiągnięto max_children”.
Plik tworzenia z odpowiednimi ustawieniami domyślnymi
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:
Obraz web to mały obraz Nginx z skopiowaną konfiguracją folderu public/ i witryny, zbudowany w tym samym potoku, co obraz PHP. Kluczowe punkty: powiąż opublikowane porty z 127.0.0.1, nigdy nie publikuj bazy danych, ustaw zasady ponownego uruchamiania, ogranicz pamięć, aby jeden kontener nie mógł wyłączyć hosta, i ogranicz rozmiar dziennika. Pamiętaj, że porty publikowane przez Docker mogą ominąć reguły zapory hosta, jak wyjaśniamy w przewodniku Cloudflare. Nigdy nie montuj gniazda Docker w kontenerze aplikacji, ponieważ daje to kontenerowi kontrolę nad hostem.
Zwolnienia i wycofania
Najlepszą cechą konfiguracji opartej na obrazie jest to, że każde wydanie jest tagiem, a wycofanie polega na „rozpoczęciu poprzedniego tagu”. Oznacz obrazy hashem zatwierdzenia Git, nigdy nie polegaj na latest i spraw, aby skrypt wydania sprawdzał stan:
#!/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
Zmiany w bazie danych wymagają ostrożności: zapisz migracje, aby poprzednia wersja kodu nadal działała z nowym schematem (najpierw dodaj kolumny, usuń je w późniejszej wersji). To właśnie sprawia, że szybkie wycofanie jest bezpieczne. Wykonaj kopię zapasową bazy danych przed wydaniem jakiejkolwiek wersji zmieniającej schemat.
Budowa i wysyłka z CI
Zbuduj obraz raz w CI i wdróż dokładnie ten artefakt. Minimalny przepływ pracy 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 }}"
Uruchom testy i analizę statyczną w tym samym potoku przed zbudowaniem obrazu i użyj klucza wdrażania, który może uruchomić tylko skrypt wydania. Dodaj skanowanie obrazu pod kątem luk w zabezpieczeniach, jeśli wymaga tego Twój profil ryzyka.
Logi, dane i bezpieczeństwo
- Dzienniki trafiają na stdout i stderr, są obracane przez Docker i, w przypadku ważnych wydarzeń, wysyłane do centralnego miejsca, aby przetrwały w kontenerze.
- Dane znajdują się poza kontenerem. Wolumin nie jest kopią zapasową. Skorzystaj z metody opisanej w naszym przewodniku po kopiach zapasowych: szyfrowana, przechowywana poza siedzibą firmy i testowana przy przywracaniu.
- Sekrety nigdy nie są zapisane w obrazach. Przekaż je w czasie wykonywania przez pliki env lub menedżera sekretów i trzymaj je z dala od Git.
- Uruchom jako użytkownik inny niż root, jeśli to możliwe, użyj systemu plików tylko do odczytu i usuń to, czego nie potrzebujesz.
- Zachowaj świeżość obrazów. Odbuduj regularnie, aby zastosować poprawki bezpieczeństwa w obrazie podstawowym i PHP.
- Zastosuj kontrole na poziomie aplikacji z naszej listy kontrolnej bezpieczeństwa PHP, ponieważ kontenery nie naprawiają niezabezpieczonego kodu.
Lista kontrolna gotowości produkcyjnej
- Obraz wieloetapowy z zależnościami produkcyjnymi, przypięty obraz podstawowy, użytkownik inny niż root.
- OPcache włączone, zarejestrowane błędy nie są wyświetlane, rozmiar php-fpm na podstawie zmierzonej pamięci.
- Utwórz plik z zasadami ponownego uruchamiania, kontrolą stanu, limitami pamięci, rotacją dzienników i brakiem publicznego portu bazy danych.
- Zwalnia poprzez tag commit-hash z automatyczną kontrolą stanu i wycofywaniem zmian.
- Kopie zapasowe i testowane przywracanie bazy danych i przesyłanych plików.
- Monitorowanie i alerty dotyczące kondycji, dysku, błędów i certyfikatów.
Jak możemy pomóc
Konteneryzujemy aplikacje PHP, konfigurujemy potoki CI i wydania oraz łączymy je z płatnościami, CRM i przesyłaniem wiadomości. Zobacz naszą usługę integracji lub zarezerwuj rozmowę, a my sprawdzimy Twój obecny proces hostingu i wydania.
Najczęściej zadawane pytania
Czy Docker jest dobry do uruchamiania PHP w środowisku produkcyjnym?
Tak, jeśli jest dobrze używany. Zapewnia identyczne środowiska, powtarzalne wydania i szybkie wycofywanie. Wymaga odpowiedniej konfiguracji: niezmiennych obrazów, kontroli stanu, limitów zasobów, rotacji logów, zarządzania sekretami i tworzenia kopii zapasowych danych poza kontenerem.
Czy baza danych powinna działać w środowisku Docker w środowisku produkcyjnym?
Może być stosowany w małych i średnich systemach, jeśli wykorzystuje stały wolumin, nigdy nie jest ujawniany publicznie i ma przetestowane kopie zapasowe. W przypadku danych krytycznych wiele zespołów preferuje usługę zarządzanej bazy danych, która obsługuje przełączanie awaryjne, aktualizacje i kopie zapasowe za Ciebie.
Jak wycofać wersję Docker ?
Oznacz każdy obraz hashem zatwierdzenia Git i zachowaj poprzedni tag. Aby wycofać, uruchom kontenery z poprzednim tagiem. Zapisz migracje baz danych, aby starszy kod nadal działał z nowszym schematem.
Ilu pracowników php-fpm powinienem skonfigurować?
Zmierz średnią pamięć jednego procesu roboczego pod obciążeniem, zdecyduj, ile pamięci może wykorzystać PHP, i podziel. Ustawienie zbyt dużej wartości powoduje zamianę, natomiast zbyt niska powoduje kolejkowanie żądań. Obejrzyj dziennik php-fpm pod kątem ostrzeżeń max_children.
Jaki jest największy błąd bezpieczeństwa Docker w aplikacjach PHP ?
Umieszczanie sekretów w obrazie lub Git i publikowanie portów baz danych w Internecie. Unikaj także uruchamiania jako root, montowania gniazda Docker w kontenerach i nigdy nie aktualizowania obrazu podstawowego.
Bezpłatna rozmowa odkrywcza
Bałagan w stacku albo zawieszona integracja - Integrono zaplanuje najmądrzejszy pierwszy ruch.
Dostajesz senior-ocenę pułapek budżetowych, zależności i tego, czy kupować, sklejać czy budować - zanim zacznie się płatna praca. CEO zostaje w pętli.