Docker voor PHP in productie: een praktische installatiegids voor zakelijke toepassingen
Docker zorgt ervoor dat een PHP applicatie op dezelfde manier draait op een laptop, zowel tijdens het testen als tijdens de productie, en maakt releases en rollbacks routine in plaats van stressvol. Het voegt ook bewegende delen toe, en een onzorgvuldige opstelling is erger dan een gewone server. Deze handleiding toont een productieklare basislijn met echte configuratie, en wanneer u zich geen zorgen hoeft te maken.
In het kort
- Docker is de moeite waard als je identieke omgevingen, snelle rollbacks en gemakkelijke onboarding wilt. Voor een enkele kleine brochuresite op shared hosting is dit meestal niet nodig.
- Bouw een onveranderlijk image in CI: build in meerdere fasen, alleen productieafhankelijkheden, niet-rootgebruiker, OPcache aan, fouten geregistreerd en nooit weergegeven.
- Vrijgeven door afbeeldingstag (de Git commit-hash), controleer de gezondheid na het starten en ga automatisch terug naar de vorige tag als de controle mislukt.
- Containers zijn wegwerpbaar, data niet: bewaar de database en uploads in volumes of beheerde services en maak er een back-up van buiten Docker.
- De meest voorkomende productiefouten: geheimen in de afbeelding, gepubliceerde databasepoorten, onbegrensde logs en php-fpm-limieten die niet overeenkomen met het geheugen dat je hebt.
Voor veel zakelijke toepassingen zijn de werkelijke kosten van hosting niet de server, maar de mensen. Een configuratie die slechts één ontwikkelaar begrijpt, een handmatige implementatie via FTP en een staging-omgeving die zich anders gedraagt dan de productie, zijn op de lange termijn allemaal duur. Containers pakken dit aan door de applicatie te verpakken met de exacte PHP versie en extensies. We draaien onze eigen PHP sites en clientsystemen op een Docker-gebaseerde stack, en deze handleiding beschrijft de basislijn van waaruit we vertrekken.
Wanneer Docker het waard is, en wanneer niet
- De moeite waard: verschillende services (web, PHP, database, wachtrij), meer dan één ontwikkelaar, een behoefte aan herhaalbare releases en snelle rollbacks, of een plan om tussen providers te wisselen.
- Niet nodig: een eenvoudige inhoudssite die zelden verandert op managed hosting. Toegevoegde complexiteit heeft een beloning nodig.
- Geen vervanging voor een goede bedrijfsvoering: je hebt nog steeds patching, backups en monitoring nodig. Zie onze back-up- en monitoringgids.
De doelarchitectuur
Een solide, eenvoudige opzet voor een PHP applicatie op één server:
- Reverse proxy en TLS aan de rand ( Nginx of Caddy op de host, of een CDN zoals Cloudflare, zie onze Cloudflare installatiehandleiding).
- Webcontainer ( Nginx) die statische bestanden serveert en PHP verzoeken doorgeeft.
- PHP-FPM container met uw code, ingebouwd in de afbeelding.
- Database in een eigen container met een persistent volume, of beter gezegd een beheerde databaseservice voor kritische data.
- Volumes alleen voor gegevens die moeten overleven: databasebestanden en gebruikersuploads.
Een Dockerfile met meerdere fasen
Een build in meerdere fasen installeert afhankelijkheden in één fase en kopieert alleen het resultaat naar een slanke runtime-image. De uiteindelijke afbeelding heeft geen Composer, compilers of testtools, waardoor deze kleiner is en aanvallers minder te gebruiken hebben:
# ---- 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
Kies bewust voor de PHP versie en controleer of deze nog steeds wordt ondersteund. In onze PHP 8-upgradehandleiding wordt uitgelegd hoe u dat kunt plannen. Maak versies van basisimages vast en update ze volgens een schema, aangezien een image die u nooit opnieuw opbouwt, bekende kwetsbaarheden bevat. Houd een .dockerignore aan die .git, .env, tests en lokale gegevens uitsluit, zodat er niets gevoeligs in de afbeelding terechtkomt.
Productie PHP en php-fpm-configuratie
# 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
Het instellen van opcache.validate_timestamps = 0 geeft de beste prestaties voor een onveranderlijke afbeelding, maar het werkt alleen als u nooit bestanden in een actieve container bewerkt: u geeft in plaats daarvan een nieuwe afbeelding vrij. Bepaal de pool op basis van reële cijfers: meet het gemiddelde geheugen van één PHP werker onder belasting, beslis hoeveel geheugen PHP op de server mag gebruiken en verdeel het. Als een werker 60 MB gebruikt en je PHP 1 GB kunt geven, is pm.max_children ongeveer 16. Als je dit veel hoger instelt, wordt de server alleen maar verwisseld of worden de kernel-processen uitgeschakeld tijdens een verkeerspiek. Te laag en wachtrij voor verzoeken. Controleer het php-fpm-logboek voor "max_children bereikt"-waarschuwingen.
Een Compose-bestand met de juiste standaardinstellingen
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:
De web is een kleine Nginx afbeelding waarin uw public/ map- en siteconfiguratie is gekopieerd, gebouwd in dezelfde pijplijn als de PHP afbeelding. Belangrijkste punten: bind gepubliceerde poorten aan 127.0.0.1, publiceer nooit de database, stel herstartbeleid in, beperk het geheugen zodat één container de host niet kan uitschakelen, en beperk de loggrootte. Houd er rekening mee dat door Docker gepubliceerde poorten de firewallregels van de host kunnen omzeilen, zoals we uitleggen in de Cloudflare handleiding. Monteer de Docker socket nooit in een applicatiecontainer, omdat deze container controle over de host geeft.
Releases en terugdraaiingen
De beste eigenschap van een op afbeeldingen gebaseerde configuratie is dat elke release een tag is, en een rollback is "start de vorige tag". Tag afbeeldingen met de Git commit hash, vertrouw nooit op latest, en laat het releasescript de gezondheid verifiëren:
#!/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
Databasewijzigingen vereisen zorg: schrijf migraties zodat de vorige versie van de code nog steeds werkt met het nieuwe schema (voeg eerst kolommen toe en verwijder ze in een latere release). Dat maakt een snelle rollback veilig. Maak een databaseback-up vóór elke release waarbij het schema wordt gewijzigd.
Bouwen en verzenden vanuit CI
Bouw de afbeelding één keer op in CI en implementeer precies dat artefact. Een minimale GitHub Actions workflow:
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 }}"
Voer uw tests en statische analyses uit in dezelfde pijplijn voordat de image wordt gebouwd, en gebruik een implementatiesleutel die alleen het releasescript kan uitvoeren. Voeg een kwetsbaarheidsscan toe als uw risicoprofiel dit vereist.
Logboeken, gegevens en beveiliging
- Logboeken gaan naar stdout en stderr, worden geroteerd door Docker en, voor alles wat belangrijk is, naar een centrale plaats verzonden, zodat ze de container overleven.
- Gegevens leven buiten de container. Een volume is geen back-up. Gebruik de aanpak uit onze back-upgids: versleuteld, off-site, op herstel getest.
- Geheimen worden nooit in beelden ingebakken. Geef ze tijdens runtime door via env-bestanden of een geheimenbeheerder en houd ze buiten Git.
- Voer het uit als een niet-rootgebruiker, gebruik waar mogelijk een alleen-lezen bestandssysteem en laat vallen wat je niet nodig hebt.
- Houd afbeeldingen fris. Herbouw regelmatig om beveiligingsoplossingen in de basisimage en PHP op te halen.
- Pas de controles op applicatieniveau uit onze PHP beveiligingschecklist toe, aangezien containers onveilige code niet repareren.
Een checklist voor productiegereedheid
- Meertrapsimage met alleen productieafhankelijkheden, vastgezette basisimage, niet-rootgebruiker.
- OPcache ingeschakeld, geregistreerde fouten worden niet weergegeven, php-fpm-formaat uit gemeten geheugen.
- Stel een bestand samen met herstartbeleid, gezondheidscontroles, geheugenlimieten, logrotatie en geen openbare databasepoort.
- Releases via commit-hash-tag met een automatische statuscheck en rollback.
- Back-ups en een getest herstel voor de database en uploads.
- Monitoring en waarschuwingen over gezondheid, schijf, fouten en certificaten.
Hoe wij kunnen helpen
We containeriseren PHP applicaties, zetten CI-pijplijnen en releases op en verbinden deze met betalingen, CRM en messaging. Bekijk onze integratieservice of boek een gesprek, zodat we uw huidige hosting- en releaseproces kunnen beoordelen.
Veelgestelde vragen
Is Docker goed voor het uitvoeren van PHP in productie?
Ja, bij goed gebruik. Het biedt identieke omgevingen, herhaalbare releases en snelle rollbacks. Het heeft de juiste configuratie nodig: onveranderlijke afbeeldingen, gezondheidscontroles, resourcelimieten, logrotatie, geheimbeheer en back-ups van gegevens buiten de container.
Moet de database in Docker in productie worden uitgevoerd?
Het kan voor kleine en middelgrote systemen zijn als het een persistent volume gebruikt, nooit openbaar wordt gemaakt en back-ups heeft getest. Voor kritieke gegevens geven veel teams de voorkeur aan een beheerde databaseservice, die failover, updates en back-ups voor u afhandelt.
Hoe draai ik een Docker release terug?
Tag elke afbeelding met de Git commit-hash en behoud de vorige tag. Om terug te draaien, start u de containers met de vorige tag. Schrijf databasemigraties zodat de oudere code nog steeds werkt met het nieuwere schema.
Hoeveel php-fpm-workers moet ik configureren?
Meet het gemiddelde geheugen van één werknemer onder belasting, bepaal hoeveel geheugen PHP mag gebruiken en verdeel het. Als u de waarde te hoog instelt, wordt er gewisseld, terwijl een te lage waarde ervoor zorgt dat er aanvragen in de wachtrij staan. Bekijk het php-fpm-logboek voor max_children-waarschuwingen.
Wat is de grootste Docker-beveiligingsfout met PHP apps?
Geheimen in de afbeelding of Git plaatsen en databasepoorten op internet publiceren. Vermijd ook om als root te draaien, de Docker socket in containers te monteren en nooit de basisimage bij te werken.
Gratis ontdekkingsgesprek
Breng een rommelige stack of vastgelopen integratie - Integrono schetst de slimste eerste zet.
U krijgt een senior blik op budgetvallen, afhankelijkheden en of u moet kopen, lijmen of bouwen - voordat betaald werk start. De CEO blijft in the loop.