Blog

Infrastructură 10 min de citit

Docker pentru PHP în producție: un ghid practic de configurare pentru aplicații de afaceri

Docker face ca o aplicație PHP să ruleze în același mod pe un laptop, în testare și în producție, și face rutine lansărilor și rollback-urilor în loc să fie stresante. De asemenea, adaugă părți mobile, iar o configurare neglijentă este mai proastă decât un server simplu. Acest ghid arată o linie de bază pregătită pentru producție, cu configurație reală și când nu ar trebui să vă deranjați.

Imagine de copertă: Docker pentru PHP în producție: un ghid practic de configurare pentru aplicații de afaceri

Pe scurt

  • Docker merită atunci când doriți medii identice, derulări rapide și integrare ușoară. Pentru un singur site de broșură mică pe găzduire partajată, este de obicei inutil.
  • Creați o imagine imuabilă în CI: construcție în mai multe etape, numai dependențe de producție, utilizator non-root, OPcache activat, erori înregistrate și niciodată afișate.
  • Eliberare prin etichetă imagine (hash-ul Git commit), verificați starea de sănătate după pornire și reveniți automat la eticheta anterioară dacă verificarea eșuează.
  • Containerele sunt de unică folosință, datele nu sunt: păstrați baza de date și încărcările în volume sau servicii gestionate și faceți copii de rezervă în afara Docker.
  • Cele mai frecvente greșeli de producție: secrete din imagine, porturi de baze de date publicate, jurnale nelimitate și limite php-fpm care nu se potrivesc cu memoria pe care o aveți.

Pentru multe aplicații de afaceri, costul real al găzduirii nu este serverul, ci oamenii. Configurarea pe care o înțelege doar un singur dezvoltator, o implementare efectuată manual prin FTP și un mediu de pregătire care se comportă diferit de producție sunt toate costisitoare pe termen lung. Containerele abordează acest lucru prin ambalarea aplicației cu versiunea și extensiile PHP exacte. Ne rulăm propriile site-uri PHP și sistemele client pe o stivă bazată pe Docker, iar acest ghid descrie linia de bază de la care pornim.

Când Docker merită și când nu

  • Merită: mai multe servicii (web, PHP, bază de date, coadă), mai mult de un dezvoltator, necesitatea de lansări repetabile și rollback-uri rapide sau un plan de mutare între furnizori.
  • Nu este necesar: un site de conținut simplu care se schimbă rareori pe găzduirea gestionată. Complexitatea adăugată necesită o răsplată.
  • Nu este un înlocuitor pentru operațiuni bune: mai aveți nevoie de corecție, backup și monitorizare. Consultați ghidul nostru de backup și monitorizare.

Arhitectura țintă

O configurare solidă și simplă pentru o aplicație PHP pe un singur server:

  • Proxy invers și TLS la margine ( Nginx sau Caddy pe gazdă sau un CDN, cum ar fi Cloudflare, consultați ghidul nostru de configurare Cloudflare).
  • Container web ( Nginx) care servește fișiere statice și transmite cereri PHP.
  • Container PHP-FPM cu codul dvs., încorporat în imagine.
  • Baza de date în propriul container cu un volum persistent, sau mai bine un serviciu de baze de date gestionat pentru date critice.
  • Volume numai pentru datele care trebuie să supraviețuiască: fișiere de baze de date și încărcări de utilizatori.

Un Dockerfile în mai multe etape

O versiune în mai multe etape instalează dependențe într-o singură etapă și copiază doar rezultatul într-o imagine de rulare subțire. Imaginea finală nu are Composer, compilatoare sau instrumente de testare, ceea ce o face mai mică și le oferă atacatorilor mai puțin de utilizat:

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

Alegeți în mod deliberat versiunea PHP și verificați dacă este încă acceptată. Ghidul nostru de actualizare PHP 8 explică cum să planificați asta. Fixați versiuni ale imaginilor de bază și actualizați-le conform unui program, deoarece o imagine pe care nu o reconstruiți niciodată acumulează vulnerabilități cunoscute. Păstrați un .dockerignore care exclude .git, .env, testele și datele locale, astfel încât nimic sensibil să nu ajungă în imagine.

Configurare PHP de producție și 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

Setarea opcache.validate_timestamps = 0 oferă cea mai bună performanță pentru o imagine imuabilă, dar funcționează numai dacă nu editați niciodată fișiere într-un container care rulează: în schimb, eliberați o nouă imagine. Măriți pool-ul din numere reale: măsurați memoria medie a unui lucrător PHP sub încărcare, decideți câtă memorie poate folosi PHP pe server și împărțiți. Dacă un lucrător folosește 60 MB și poți da PHP 1 GB, pm.max_children este aproximativ 16. Setarea mult mai mare face doar schimbul de server sau procesele de distrugere a nucleului în timpul unui vârf de trafic. Prea scăzut și coada de solicitări. Verificați jurnalul php-fpm pentru avertismente „max_children reached”.

Un fișier Compune cu valorile implicite corecte

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:

Imaginea web este o imagine mică Nginx cu configurația public/ dosarului și a site-ului copiate, construită în aceeași conductă ca imaginea PHP. Puncte cheie: legați porturile publicate la 127.0.0.1, nu publicați niciodată baza de date, setați politici de repornire, limitați memoria astfel încât un container să nu poată elimina gazda și limitați dimensiunea jurnalului. Amintiți-vă că porturile publicate de Docker pot ocoli regulile firewall-ului gazdei, așa cum explicăm în ghidul Cloudflare. Nu montați niciodată soclu-ul Docker într-un container de aplicații, deoarece îi oferă acel container control asupra gazdei.

Lansări și derulări

Cea mai bună caracteristică a unei configurări bazate pe imagini este că fiecare lansare este o etichetă, iar o retragere este „pornirea etichetei anterioare”. Etichetați imaginile cu hashul Git commit, nu vă bazați niciodată pe latest și faceți ca scriptul de lansare să verifice starea de sănătate:

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

Modificările bazei de date necesită îngrijire: scrieți migrațiile, astfel încât versiunea anterioară a codului să funcționeze în continuare cu noua schemă (adăugați mai întâi coloane, eliminați-le într-o ediție ulterioară). Acesta este ceea ce face o derulare rapidă în siguranță. Faceți o copie de rezervă a bazei de date înainte de orice lansare care modifică schema.

Construire și expediere de la CI

Construiți imaginea o dată în CI și implementați exact acel artefact. Un flux de lucru minim 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 }}"

Rulați testele și analiza statică în aceeași conductă înainte ca imaginea să fie construită și utilizați o cheie de implementare care poate rula doar scriptul de lansare. Adăugați o scanare a vulnerabilității imaginii dacă profilul dvs. de risc o cere.

Jurnale, date și securitate

  • Jurnalele merg la stdout și stderr, sunt rotite de Docker și, pentru orice lucru important, expediate într-un loc central, astfel încât să supraviețuiască containerului.
  • Datele se află în afara containerului. Un volum nu este o copie de rezervă. Utilizați abordarea din ghidul nostru de backup: criptat, în afara site-ului, restaurat-testat.
  • Secretele nu sunt niciodată coapte în imagini. Treceți-le în timpul execuției prin fișierele env sau printr-un manager de secrete și păstrați-le departe de Git.
  • Rulați ca utilizator non-root, utilizați un sistem de fișiere numai în citire acolo unde este posibil și aruncați ceea ce nu aveți nevoie.
  • Păstrați imaginile proaspete. Reconstruiți regulat pentru a obține remedieri de securitate în imaginea de bază și PHP.
  • Aplicați controalele la nivel de aplicație din lista noastră de verificare a securității PHP, deoarece containerele nu remediază codul nesigur.

O listă de verificare a pregătirii pentru producție

  1. Imagine în mai multe etape cu dependențe de producție numai, imagine de bază fixată, utilizator non-root.
  2. OPcache activat, erorile înregistrate nu sunt afișate, dimensiunea php-fpm din memoria măsurată.
  3. Compuneți fișierul cu politici de repornire, verificări de sănătate, limite de memorie, rotație de jurnal și fără port public al bazei de date.
  4. Lansări prin etichetă commit-hash cu verificare automată a stării de sănătate și rollback.
  5. Backup-uri și o restaurare testată pentru baza de date și încărcări.
  6. Monitorizare și alerte privind starea de sănătate, disc, erori și certificate.

Cum putem ajuta

Containerizam aplicațiile PHP, instalăm conducte și lansări CI și le conectăm la plăți, CRM și mesagerie. Vedeți serviciul nostru de integrări sau rezervați un apel și vă vom examina procesul actual de găzduire și lansare.

Întrebări frecvente

Este Docker bun pentru a rula PHP în producție?

Da, atunci când este bine folosit. Oferă medii identice, lansări repetabile și rollback-uri rapide. Are nevoie de o configurație adecvată: imagini imuabile, verificări de sănătate, limite de resurse, rotație de jurnal, gestionare a secretelor și copii de siguranță ale datelor din afara containerului.

Ar trebui să ruleze baza de date în Docker în producție?

Se poate aplica pentru sistemele mici și mijlocii dacă folosește un volum persistent, nu este niciodată expus public și are copii de rezervă testate. Pentru datele critice, multe echipe preferă un serviciu de baze de date gestionate, care se ocupă de failover-ul, actualizările și backup-urile pentru dvs.

Cum retrag o versiune Docker ?

Etichetați fiecare imagine cu hashul Git commit și păstrați eticheta anterioară. Pentru a derula înapoi, începeți containerele cu eticheta anterioară. Scrieți migrarea bazei de date, astfel încât codul mai vechi să funcționeze în continuare cu schema mai nouă.

Câți lucrători php-fpm ar trebui să configurez?

Măsurați memoria medie a unui lucrător sub sarcină, decideți câtă memorie poate folosi PHP și împărțiți. Setarea unei valori prea mare determină schimbarea, în timp ce prea scăzută cauzează solicitări puse în coadă. Urmăriți jurnalul php-fpm pentru avertismente max_children.

Care este cea mai mare greșeală de securitate Docker cu aplicațiile PHP ?

Introducerea de secrete în imagine sau Git și publicarea porturilor de baze de date pe internet. De asemenea, evitați să rulați ca root, să montați soclul Docker în containere și să nu actualizați niciodată imaginea de bază.

Apel de descoperire gratuit

Adu un stack haotic sau o integrare blocată - Integrono mapează cel mai inteligent prim pas.

Primești o evaluare senior pe capcane de buget, dependențe și dacă merită să cumperi, să lipești sau să construiești - înainte să înceapă munca plătită. CEO-ul rămâne în buclă.