Blogg

Infrastruktur 9 min läsning

Docker för PHP i produktion: en praktisk installationsguide för affärsapplikationer

Docker gör att en PHP applikation körs på samma sätt på en bärbar dator, i testning och i produktion, och gör releaser och återställningar till rutin istället för stressande. Den lägger också till rörliga delar, och en slarvig installation är värre än en vanlig server. Den här guiden visar en produktionsklar baslinje med riktig konfiguration, och när du inte borde bry dig.

Omslagsbild: Docker för PHP i produktion: en praktisk installationsguide för affärsapplikationer

Kortfattat

  • Docker är värt det när du vill ha identiska miljöer, snabba återställningar och enkel onboarding. För en enda liten broschyrsajt om delad hosting är det vanligtvis onödigt.
  • Bygg en oföränderlig bild i CI: bygg i flera steg, endast produktionsberoende, icke-rootanvändare, OPcache på, fel loggade och aldrig visade.
  • Släpp efter bildtagg (Git commit-hash), kontrollera hälsan efter start och rulla tillbaka till föregående tag automatiskt om kontrollen misslyckas.
  • Behållare är för engångsbruk, data är det inte: behåll databasen och uppladdningar i volymer eller hanterade tjänster och säkerhetskopiera dem utanför Docker.
  • De vanligaste produktionsmisstagen: hemligheter i bilden, publicerade databasportar, obegränsade loggar och php-fpm-gränser som inte matchar minnet du har.

För många affärsapplikationer är den verkliga kostnaden för hosting inte servern utan människorna. Installation som bara en utvecklare förstår, en driftsättning som görs för hand över FTP och en iscensättningsmiljö som beter sig annorlunda än produktion är alla dyra i längden. Behållare åtgärdar detta genom att paketera applikationen med dess exakta PHP version och tillägg. Vi kör våra egna PHP sajter och klientsystem på en Docker-baserad stack, och den här guiden beskriver baslinjen vi utgår från.

När Docker är värt det, och när det inte är det

  • Värt det: flera tjänster (webb, PHP, databas, kö), mer än en utvecklare, ett behov av repeterbara utgåvor och snabba återställningar, eller en plan för att flytta mellan leverantörer.
  • Inte nödvändigt: en enkel innehållswebbplats som sällan ändras på hanterad värd. Extra komplexitet kräver utdelning.
  • Inte en ersättning för bra operationer: du behöver fortfarande patchning, säkerhetskopiering och övervakning. Se vår guide för säkerhetskopiering och övervakning.

Målarkitekturen

En solid, enkel installation för en PHP applikation på en server:

  • Omvänd proxy och TLS vid kanten ( Nginx eller Caddy på värden, eller en CDN som Cloudflare, se vår Cloudflare installationsguide).
  • Webbbehållare ( Nginx) som serverar statiska filer och skickar PHP förfrågningar vidare.
  • PHP-FPM behållare med din kod, inbyggd i bilden.
  • Databas i sin egen behållare med en beständig volym, eller bättre en hanterad databastjänst för kritisk data.
  • Volymer endast för data som måste överleva: databasfiler och användaruppladdningar.

En flerstegs Dockerfile

En flerstegsbyggnad installerar beroenden i ett steg och kopierar endast resultatet till en smal körtidsbild. Den slutliga bilden har ingen Composer, kompilatorer eller testverktyg, vilket gör den mindre och ger angripare mindre att använda:

# ---- 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

Välj PHP versionen medvetet och kontrollera att den fortfarande stöds. Vår PHP 8 uppgraderingsguide förklarar hur man planerar det. Fäst versioner av basbilder och uppdatera dem enligt ett schema, eftersom en bild som du aldrig återskapar samlar på sig kända sårbarheter. Behåll en .dockerignore som utesluter .git, .env, tester och lokal data, så att inget känsligt hamnar i bilden.

Produktion PHP och php-fpm konfiguration

# 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

Att ställa in opcache.validate_timestamps = 0 ger den bästa prestandan för en oföränderlig bild, men det fungerar bara om du aldrig redigerar filer i en pågående behållare: du släpper en ny bild istället. Storlek på poolen från reella siffror: mät det genomsnittliga minnet för en PHP arbetare under belastning, bestäm hur mycket minne PHP får använda på servern och dela. Om en arbetare använder 60 MB och du kan ge PHP 1 GB, är pm.max_children cirka 16. Om du ställer in det mycket högre gör det bara att serverbytet eller kärnan dödar processer under en trafikspike. För lågt och begär kö. Kontrollera php-fpm-loggen för "max_children reach"-varningar.

En Compose-fil med rätt standardinställningar

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 är en liten Nginx bild med din public/ mapp och webbplatskonfiguration kopierad, byggd i samma pipeline som PHP avbildningen. Nyckelpunkter: bind publicerade portar till 127.0.0.1, publicera aldrig databasen, ställ in omstartspolicyer, begränsa minnet så att en behållare inte kan ta ner värden, och begränsa loggstorleken. Kom ihåg att portar publicerade av Docker kan kringgå reglerna för värdbrandväggen, som vi förklarar i Cloudflare-guiden. Montera aldrig Docker uttaget i en applikationsbehållare, eftersom det ger den behållaren kontroll över värden.

Släppningar och återställningar

Det bästa med en bildbaserad installation är att varje utgåva är en tagg, och en återställning är "starta föregående tagg". Tagga bilder med Git commit-hash, lita aldrig på latest, och få releaseskriptet att verifiera hälsan:

#!/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

Databasändringar kräver omsorg: skriv migrering så att den tidigare versionen av koden fortfarande fungerar med det nya schemat (lägg till kolumner först, ta bort dem i en senare version). Det är det som gör en snabb återställning säker. Ta en databassäkerhetskopiering innan någon version som ändrar schemat.

Byggande och frakt från CI

Bygg bilden en gång i CI och distribuera exakt den artefakten. Ett minimalt arbetsflöde 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 }}"

Kör dina tester och statiska analyser i samma pipeline innan bilden byggs, och använd en distributionsnyckel som bara kan köra releaseskriptet. Lägg till en bildsårbarhetsskanning om din riskprofil kräver det.

Loggar, data och säkerhet

  • Loggar går till stdout och stderr, roteras av Docker och, för allt viktigt, skickas till en central plats så att de överlever containern.
  • Data finns utanför behållaren. En volym är inte en säkerhetskopia. Använd metoden i vår säkerhetskopieringsguide: krypterad, off-site, återställningstestad.
  • Hemligheter är aldrig inbakade i bilder. Skicka dem under körning genom env-filer eller en hemlighetshanterare och håll dem borta från Git.
  • Kör som en icke-rootanvändare, använd ett skrivskyddat filsystem där det är möjligt och släpp det du inte behöver.
  • Håll bilderna fräscha. Bygg om regelbundet för att plocka upp säkerhetsfixar i basbilden och PHP.
  • Använd kontrollerna på applikationsnivå från vår PHP säkerhetschecklista eftersom behållare inte fixar osäker kod.

En checklista för produktionsberedskap

  1. Flerstegsbild med endast produktionsberoende, fäst basbild, icke-rootanvändare.
  2. OPcache aktiverad, fel loggade visas inte, php-fpm storlek från uppmätt minne.
  3. Komponera fil med omstartspolicyer, hälsokontroller, minnesgränser, loggrotation och ingen offentlig databasport.
  4. Släpps med commit-hash-tagg med en automatisk hälsokontroll och återställning.
  5. Säkerhetskopieringar och en testad återställning för databasen och uppladdningar.
  6. Övervakning och varningar om hälsa, disk, fel och certifikat.

Hur vi kan hjälpa till

Vi containeriserar PHP applikationer, ställer upp CI-pipelines och releaser och kopplar dem till betalningar, CRM och meddelanden. Se vår integrationstjänst eller boka ett samtal så granskar vi din nuvarande värd- och releaseprocess.

Vanliga frågor

Är Docker bra för att köra PHP i produktion?

Ja, när den används väl. Det ger identiska miljöer, repeterbara releaser och snabba återställningar. Den behöver korrekt konfiguration: oföränderliga bilder, hälsokontroller, resursbegränsningar, loggrotation, hemlighetshantering och säkerhetskopiering av data utanför behållaren.

Ska databasen köras i Docker i produktion?

Det kan för små och medelstora system om det använder en beständig volym, aldrig exponeras offentligt och har testat säkerhetskopior. För kritisk data föredrar många team en hanterad databastjänst, som hanterar failover, uppdateringar och säkerhetskopieringar åt dig.

Hur återställer jag en Docker version?

Tagga varje bild med Git commit-hash och behåll den föregående taggen. För att rulla tillbaka, starta behållarna med föregående tagg. Skriv databasmigreringar så att den äldre koden fortfarande fungerar med det nyare schemat.

Hur många php-fpm-arbetare ska jag konfigurera?

Mät det genomsnittliga minnet för en arbetare under belastning, bestäm hur mycket minne PHP får använda och dela. Att ställa in värdet för högt orsakar byte, medan för lågt orsakar förfrågningar i kö. Titta på php-fpm-loggen för max_children-varningar.

Vilket är det största Docker säkerhetsmisstaget med PHP-appar?

Lägga hemligheter i bilden eller Git och publicera databasportar till internet. Undvik också att köra som root, montera Docker-uttaget i behållare och aldrig uppdatera basbilden.

Gratis upptäcktssamtal

Ta med en rörig stack eller en fastnad integration - Integrono kartlägger det smartaste första steget.

Ni får en senior bedömning av budgetfällor, beroenden och om ni ska köpa, limma eller bygga - innan betalt arbete startar. CEO stannar i loopen.