Dolazim iz vercel dev sveta. Kucam komandu, dev server radi. Hot reload. HTTPS. Sve "just works."
WordPress lokalni development experience je… drugačiji.
MAMP. Local by Flywheel. XAMPP. Vagrant. Ili, ako ste masohista, ručna instalacija Apache + PHP + MySQL.
Hteo sam setup koji liči na ono što sam navikao. Docker je bio odgovor.
Šta sam hteo
- Jedan command za start —
pnpm dev:docker:starti sve radi - HTTPS lokalno — Jer neke stvari (Turnstile, cookies) zahtevaju HTTPS
- Hot reload za temu — Promenim PHP/CSS, osveži se
- Email catching — Da vidim email-ove bez slanja
- Production parity — Isto što lokalno, to u produkciji
Docker Compose: 6 servisa
# docker-compose.local.yml
services:
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
# Reverse proxy, HTTPS termination
wordpress:
image: wordpress:6-php8.2-fpm
volumes:
- ./apps/studenti-theme:/var/www/html/wp-content/themes/studently-theme
- ./apps/studenti-plugin:/var/www/html/wp-content/plugins/studenti-functions
# PHP-FPM, theme/plugin mounted from repo
mariadb:
image: mariadb:11
volumes:
- db_data:/var/lib/mysql
# Database with persistent volume
redis:
image: redis:7-alpine
# Object cache
node-api:
build: ./apps/studenti-node-api
volumes:
- ./apps/studenti-node-api:/app
# Hot reload for Node API
mailpit:
image: axllent/mailpit
ports:
- "8025:8025"
# Email catching UI
Jedan docker compose up i sve radi.
Kako servisi komuniciraju
┌─────────────────────────────┐
│ Korisnik (Browser) │
└──────────────┬────────────────┘
│ HTTPS (:443)
┌──────────────▼────────────────┐
│ Caddy (reverse proxy) │
│ Automatski HTTPS, routing │
└───────┬──────────────┬─────────┘
│ │
studenti.local│ │api.studenti.local
│ │
┌───────────▼───┐ ┌──────▼──────────┐
│ WordPress │ │ Node API │
│ (PHP-FPM) │ │ (Hono + TS) │
└──┬─────┬──────┘ └──────┬──────────┘
│ │ │
┌────────▼┐ ┌─▼───────┐ ┌────▼─────┐
│ MariaDB │ │ Redis │ │ ClamAV │
│ (baza) │ │ (keš) │ │ (virusi) │
└──────────┘ └──────────┘ └──────────┘
┌─────────────────────────────┐
│ Mailpit (lokalno) / SMTP2Go │
│ (produkcija) — email │
└─────────────────────────────┘
Svaki servis je izolovan u svom kontejneru. Caddy prima sve zahteve i prosleđuje ih na pravi servis.
Caddy: Zašto ne nginx
Nginx je industrijski standard. Ali Caddy nudi ozbiljne prednosti koje daleko prevazilaze "lakšu konfiguraciju":
Automatski HTTPS — svuda
Lokalno: tls internal generiše sertifikat. U produkciji: automatski Let's Encrypt sa nula konfiguracije. Obnova sertifikata? Automatska. Nema crontab-a, nema certbot-a, nema zaboravljenih isteklih sertifikata u 3 ujutru.
HTTP/2 i HTTP/3 iz kutije
Caddy automatski omogućava HTTP/2 za sve HTTPS konekcije i HTTP/3 (QUIC) kad je dostupan. Nginx zahteva eksplicitnu konfiguraciju i rekompajliranje za HTTP/3.
Drastično jednostavnija konfiguracija
Caddy:
studenti.local {
tls internal
reverse_proxy wordpress:9000
}
api.studenti.local {
tls internal
reverse_proxy node-api:3005
}
Ekvivalent u nginx-u zahteva 30+ linija: server blok, listen direktive, ssl_certificate putanje, proxy_pass, proxy_set_header za svaki servis, i posebnu konfiguraciju za WebSocket upgrade.
Ugrađeni reverse proxy
Nema potrebe za dodatnim modulima. reverse_proxy je deo jezgra Caddy servera, sa automatskim health check-ovima, load balancing-om, i header rewriting-om.
Memorijski siguran
Caddy je napisan u Go-u (memorijski siguran jezik), za razliku od nginx-a koji je u C-u. Manje šansi za buffer overflow ranjivosti.
Za nginx, trebalo bi: generisati sertifikate, konfigurisati SSL parametre, podesiti proxy headere, restartovati posle svake promene. Caddy: 6 linija i gotovo.
Theme/Plugin mounting
Ključ za hot reload: mount iz repozitorijuma.
volumes:
- ./apps/studenti-theme:/var/www/html/wp-content/themes/studently-theme
- ./apps/studenti-plugin:/var/www/html/wp-content/plugins/studenti-functions
Promenim fajl u apps/studenti-theme/. WordPress odmah vidi promenu.
Git je source of truth. WordPress čita iz Git-a. Nikada ne editujem fajlove "u WordPress-u."
Mailpit: Email bez slanja
Svaki WordPress sajt šalje email-ove. Verifikacija, notifikacije, admin alerts.
U development-u, ne želite da šaljete prave email-ove. Želite da ih VIDITE.
Mailpit hvata sve email-ove i prikazuje u web UI na localhost:8025.
WordPress → wp_mail() → Mailpit → Vi vidite email u browseru
Nikakva konfiguracija u WordPress-u. Mailpit je SMTP server koji hvata sve.
Redis: 50-70% manje upita
WordPress voli database upite. Svaka stranica, desetine upita.
Redis object cache keširaj rezultate. Sledeći request, Redis vraća umesto MySQL-a.
// wp-config.php
define('WP_REDIS_HOST', 'redis');
Plugin: Redis Object Cache (free).
Rezultat: Drastično manje database load-a. Brže stranice.
Production vs. Local razlike
| Aspekt | Local | Production |
|---|---|---|
| HTTPS | mkcert self-signed | Let's Encrypt (automatski) |
| Mailpit | SMTP2Go | |
| Cron | wp-cron sidecar | systemd timer |
| Baza | MariaDB kontejner | MariaDB na volume-u |
| Keš | Redis (osnovni) | Redis (optimizovan) |
Production ima dodatne optimizacije:
- OPcache enabled
- 8GB MariaDB buffer pool
- Redis persistence
- fail2ban, UFW
WP-Cron: Zašto systemd
WordPress pseudo-cron: scheduled tasks se izvršavaju kada neko poseti sajt.
Problem: Nema poseta = nema cron-a. Scheduled posts se ne objave.
Rešenje 1 (local): Sidecar container koji poziva wp-cron.php.
Rešenje 2 (production): systemd timer.
# /etc/systemd/system/wp-cron.timer
[Timer]
OnCalendar=*:*:00
Persistent=true
[Install]
WantedBy=timers.target
Svaki minut, systemd poziva wp-cron.php. Scheduled posts se objave na vreme.
Database sync: Production → Local
Razvoj sa pravim podacima je bolji od dummy podataka.
pnpm dev:sync:prod:db
Šta radi:
- Export iz production (via Docker exec)
- Download via SCP
- Import u local MariaDB
- Search-replace (studenti.rs → studenti.local)
- Fix serialized data (Serbian characters)
- Clear caches
Serbian karakteri (š, ž, ć) mogu da pokvare PHP serialization. Script to popravlja.
Scripts: 20+ za infrastrukturu
infrastructure/scripts/
├── dev-start.sh # Start local Docker
├── dev-stop.sh # Stop Docker
├── dev-logs.sh # View logs
├── db-import-local.sh # Import SQL to local
├── sync-prod-to-local.sh # Full production sync
├── staging-bootstrap.sh # Setup new staging VPS
├── deploy-staging.sh # Deploy to staging
├── staging-sync-db.sh # Sync DB to staging
├── production-sync-db.sh # Sync DB to production
└── ...
Svaka operacija je skriptovana. Ponovljiva. Dokumentovana.
Gap koji ostaje
Docker rešava mnogo toga. Ali neki WordPress DX problemi ostaju:
Nema hot reload za PHP. Morate refresh. Livereload pomaže za CSS, ali PHP je uvek refresh.
Debug je ručan. Xdebug + IDE konfiguracija. Funkcioniše, ali nije "out of the box."
Plugin conflicts. WordPress ekosistem problem, ne Docker problem.
Database heavy. Čak i sa Redis, WordPress voli upite.
Docker premošćuje gap. Ne eliminiše ga.
WordPress DX gap je realan. Dolazak iz modernog steka je bolan.
Ali Docker ga može premostiti:
- Jedan command za start
- HTTPS lokalno (Caddy + mkcert)
- Theme/plugin hot reload (volume mounts)
- Email catching (Mailpit)
- Production parity
Setup zahteva vreme na početku. Ali posle toga, pnpm dev:docker:start i radite.