Prečo Docker namiesto XAMPP alebo lokálneho PHP
XAMPP a Laragon sú skvelé na rýchly štart. Ale pri produkčnom projekte narazíte na problém: dev prostredie sa odlišuje od servera. Iná verzia PHP, iná konfigurácia MySQL, chýbajúce rozšírenia. Docker tento rozdiel eliminuje.
S Dockerom definujete celý stack v jednom súbore. Každý vývojár, CI/CD pipeline aj produkčný server spúšťajú identický kontajner. Výsledok: menej „works on my machine" bugov a reprodukovateľné deploymenty.
Štruktúra projektu
Pre Laravel alebo vlastnú PHP aplikáciu odporúčam túto adresárovú štruktúru:
projekt/
├── docker/
│ ├── nginx/
│ │ └── default.conf
│ └── php/
│ └── local.ini
├── src/ ← váš PHP kód (Laravel, atď.)
├── Dockerfile
├── docker-compose.yml
├── docker-compose.prod.yml
└── .env
docker-compose.yml pre lokálny vývoj
Toto je základ pre lokálne prostredie. Všetky zmeny kódu sa ihneď prejavia vďaka volume mountu — bez restartu kontajnera.
services:
app:
build:
context: .
target: dev
volumes:
- ./src:/var/www/html
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
depends_on:
- db
- redis
networks:
- backend
nginx:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
networks:
- backend
db:
image: mariadb:11.4
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MARIADB_DATABASE: ${DB_DATABASE}
MARIADB_USER: ${DB_USERNAME}
MARIADB_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks:
- backend
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
networks:
- backend
volumes:
db_data:
networks:
backend:
Multi-stage Dockerfile
Multi-stage build je kľúčový pre produkciu. Výsledný image neobsahuje dev závislosti, čím je výrazne menší a bezpečnejší.
# Dockerfile
FROM php:8.4-fpm-alpine AS base
RUN apk add --no-cache \
libpng-dev libjpeg-turbo-dev libwebp-dev libavif-dev \
oniguruma-dev libzip-dev icu-dev libxml2-dev \
&& docker-php-ext-configure gd --with-jpeg --with-webp --with-avif \
&& docker-php-ext-install \
pdo_mysql mbstring zip gd intl opcache bcmath exif
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/
RUN install-php-extensions redis
WORKDIR /var/www/html
# ─── Dev stage ──────────────────────────────────────────────
FROM base AS dev
RUN apk add --no-cache git unzip nodejs npm \
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
COPY docker/php/local.ini /usr/local/etc/php/conf.d/local.ini
# ─── Build stage ─────────────────────────────────────────────
FROM dev AS build
COPY src/ .
RUN composer install --no-dev --optimize-autoloader --no-interaction \
&& npm ci --prefix . \
&& npm run build --prefix . \
&& rm -rf node_modules
# ─── Production stage ────────────────────────────────────────
FROM base AS prod
COPY docker/php/prod.ini /usr/local/etc/php/conf.d/local.ini
COPY --from=build /var/www/html .
RUN chown -R www-data:www-data /var/www/html/storage \
&& chmod -R 755 /var/www/html/storage
USER www-data
local.ini zapnite display_errors = On a xdebug. V prod.ini naopak: display_errors = Off, opcache.enable = 1, opcache.validate_timestamps = 0.
Nginx konfigurácia pre PHP-FPM
# docker/nginx/default.conf
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.php;
client_max_body_size 20M;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
location ~* \.(css|js|png|jpg|jpeg|gif|webp|avif|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location ~ /\.(?!well-known) {
deny all;
}
}
Spustenie a bežné príkazy
# Spustenie (lokálne)
docker compose up -d
# Vstup do PHP kontajnera
docker compose exec app sh
# Artisan príkazy
docker compose exec app php artisan migrate
docker compose exec app php artisan queue:work
# Composer
docker compose exec app composer install
# Logy
docker compose logs -f app
docker compose logs -f nginx
.env nikdy nepridávajte do git repozitára. Použite .env.example s prázdnymi hodnotami a .gitignore pre .env.
Produkčný docker-compose.prod.yml
Pre produkciu použite override súbor, ktorý prepíše lokálnu konfiguráciu:
# docker-compose.prod.yml
services:
app:
build:
context: .
target: prod
restart: unless-stopped
environment:
APP_ENV: production
APP_DEBUG: "false"
volumes: [] # žiadne volume mounty — kód je v image
nginx:
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/prod.conf:/etc/nginx/conf.d/default.conf
- /etc/letsencrypt:/etc/letsencrypt:ro
restart: unless-stopped
Spustenie na VPS:
# Build a spustenie produkčného stacku
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
# Zero-downtime update
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app
CI/CD pipeline — GitHub Actions
Automatický deployment po push na main vetvu:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build --target prod -t app:${{ github.sha }} .
- name: Push to registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker tag app:${{ github.sha }} ghcr.io/${{ github.repository }}:latest
docker push ghcr.io/${{ github.repository }}:latest
- name: Deploy na VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_KEY }}
script: |
cd /var/www/projekt
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app
docker compose exec -T app php artisan migrate --force
Záver: Docker = reprodukovateľnosť a spokojnosť
Docker nie je len módny nástroj. Je to riešenie reálneho problému — rozdielnych prostredí. Raz nastavený stack funguje identicky pre každého vývojára, v každom CI systéme, na každom VPS. Investícia niekoľkých hodín do správnej konfigurácie šetrí desiatky hodín debugovania „produkčných" bugov.
Odporúčam začať lokálnym docker compose up a postupne pridávať produkčné vrstvy — nie naopak.
Potrebujete nastaviť CI/CD alebo Docker pre váš PHP projekt?
Navrhnem kompletný DevOps stack — od lokálneho vývoja po automatický deployment. Odpoveď do 24 hodín v pracovný deň.
Nezáväzný dopyt