- Remove adminer service (not used with PostgreSQL) - Rewrite 03-DEPLOYMENT.md: Docker Compose is the only supported method - Add reverse proxy examples (Nginx, Caddy, Cloudflare Tunnel) - Document trusted proxy configuration for Laravel - Add production checklist and autoscript env var documentation - Remove all Makefile references (not recommended)
7.5 KiB
Deployment & Infrastructure
Architecture Overview
Internet → Reverse Proxy (443) → nginx container (80) → php-fpm container (9000) → postgres container (5432)
SIDES is deployed exclusively via Docker Compose. The stack consists of 4 services:
| Service | Image | Purpose |
|---|---|---|
app |
Custom (php:8.2-fpm) | Laravel application (PHP-FPM) |
postgres |
postgres:16 | PostgreSQL database |
web |
nginx:stable-alpine | Reverse proxy to PHP-FPM, serves static assets |
pgadmin |
dpage/pgadmin4 | Database management UI (optional) |
Prerequisites
- Docker Engine ^20.x
- Docker Compose v2+
- A reverse proxy in front (Nginx, Caddy, Traefik, Cloudflare Tunnel, etc.) for TLS termination
Environment Configuration
Two .env files control the stack:
Root .env — Docker Compose variables
Located at the project root. Controls container-level settings and credential defaults.
POSTGRES_DB="sides_db"
POSTGRES_USER="tck"
POSTGRES_PASSWORD="<your_strong_password>"
PGADMIN_EMAIL="admin@example.com"
PGADMIN_PASSWORD="<your_pgadmin_password>"
FIREBASE_PROJECT_ID="sides-b4abb"
FIREBASE_CREDENTIALS="storage/app/firebase/sides-b4abb-3604a7cf7584.json"
FCM_TOPIC_RAINFALL_WARNING="rainfall_warning"
FCM_TOPIC_RAINFALL_DANGER="rainfall_danger"
FCM_TOPIC_WATERLEVEL_ALERT="waterlevel_alert"
FCM_TOPIC_WATERLEVEL_DANGER="waterlevel_danger"
ADMIN_EMAIL="admin@example.com"
ADMIN_PASSWORD="<set_a_strong_password>"
FTP_SERVER="myvscada.com"
FTP_USERNAME="tck"
FTP_PASSWORD="<your_ftp_password>"
Application .env — Laravel variables
Located at src/.env. This file references Docker service names (e.g. DB_HOST=postgres) so Laravel connects to containers, not external hosts.
Key settings:
APP_NAME=SIDES
APP_ENV=production
APP_DEBUG=false
APP_URL=https://sides.tck.com.my
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=${POSTGRES_DB}
DB_USERNAME=${POSTGRES_USER}
DB_PASSWORD=${POSTGRES_PASSWORD}
SESSION_DRIVER=database
CACHE_STORE=database
QUEUE_CONNECTION=database
Quick Start
# 1. Clone the repository
git clone <repo-url> && cd tckdev
# 2. Configure environment
cp .env.example .env
# Edit .env with your actual passwords and settings
# 3. Configure Laravel env
cp src/.env.example src/.env
# Edit src/.env — ensure DB_HOST=postgres (the Docker service name)
# 4. Build and start
docker compose up -d --build
The app container's entrypoint automatically runs on first start:
composer install(ifvendor/missing)npm install && npm run build(ifpublic/build/missing)php artisan migrate --force(ifRUN_MIGRATIONS=true)php artisan db:seed --force(ifRUN_SEEDER=true)- Caches config, routes, and views
- Starts PHP-FPM
Common Operations
docker compose ps # Check container status
docker compose logs app # View Laravel logs
docker compose logs app --follow # Tail Laravel logs
docker compose logs postgres # View database logs
docker compose exec app bash # Shell into app container
docker compose exec app php artisan migrate # Run migrations
docker compose exec app php artisan migrate:fresh --seed # Reset DB
docker compose exec app php artisan db:seed # Run seeders
docker compose exec app php artisan tinker # Interactive REPL
docker compose exec app php artisan test # Run tests
docker compose down # Stop all containers
docker compose down --volumes # Stop and delete data volumes
docker compose up -d --build # Rebuild and restart
Reverse Proxy Setup
The web container (nginx) listens on port 80 internally. A reverse proxy in front handles TLS termination and forwards traffic to it.
The nginx container does not handle HTTPS itself — it expects plain HTTP from the reverse proxy. Configure your reverse proxy to:
- Listen on port 443 with your TLS certificate
- Forward to
localhost:80(or the Docker host's port 80) - Set the
X-Forwarded-Protoheader so Laravel generateshttps://URLs - Set the
X-Forwarded-Hostheader with the original hostname
Example: Nginx Reverse Proxy
On the host machine running Docker, add a server block:
server {
listen 443 ssl http2;
server_name sides.tck.com.my;
ssl_certificate /etc/ssl/certs/sides.tck.com.my.crt;
ssl_certificate_key /etc/ssl/private/sides.tck.com.my.key;
location / {
proxy_pass http://127.0.0.1:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Example: Caddy
sides.tck.com.my {
reverse_proxy localhost:80
}
Caddy handles TLS automatically via Let's Encrypt.
Example: Cloudflare Tunnel
cloudflared tunnel --hostname sides.tck.com.my --url http://localhost:80
Laravel Trusted Proxy
Laravel must trust the proxy headers to generate correct URLs. The app already has URL::forceScheme('https') enabled in AppServiceProvider. Ensure APP_URL in src/.env uses https://:
APP_URL=https://sides.tck.com.my
If using a load balancer or multi-hop proxy, configure TrustProxies middleware:
// src/app/Http/Middleware/TrustProxies.php
protected $proxies = '*';
protected $headers = Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO;
Access Points
| Service | Internal | External (via reverse proxy) |
|---|---|---|
| Application | web:80 |
https://sides.tck.com.my |
| pgAdmin | pgadmin:80 |
http://<host>:5050 |
| PostgreSQL | postgres:5432 |
localhost:5432 (optional, for external tools) |
Security note: pgAdmin (port 5050) and PostgreSQL (port 5432) should not be exposed to the public internet. Restrict access via firewall rules or only publish to 127.0.0.1:
# docker-compose.yml — restrict to localhost only
ports:
- "127.0.0.1:5050:80" # pgAdmin
- "127.0.0.1:5432:5432" # PostgreSQL
Data Persistence
Two named volumes store persistent data:
| Volume | Mount Point | Purpose |
|---|---|---|
pgdata |
/var/lib/postgresql/data |
PostgreSQL data |
pgadmin_data |
/var/lib/pgadmin |
pgAdmin sessions and config |
The src/ directory is bind-mounted into both app and web containers at /var/www/html.
Autoscript Deployment
The Python data pipeline (autoscript/sidesdecode.py) connects to PostgreSQL via environment variables. When running it outside Docker (e.g. as a cron job on the host):
export PG_HOST=localhost
export PG_PORT=5432
export PG_DATABASE=sides_db
export PG_USER=tck
export PG_PASSWORD=<your_password>
export FTP_SERVER=myvscada.com
export FTP_USERNAME=tck
export FTP_PASSWORD=<your_ftp_password>
python3 autoscript/sidesdecode.py
When running inside Docker on the same network, use PG_HOST=postgres (the service name).
Production Checklist
APP_ENV=productionandAPP_DEBUG=falseinsrc/.envAPP_URLuseshttps://- All passwords in
.envchanged from defaults ADMIN_PASSWORDset to a strong value in root.env- TLS configured on reverse proxy
- pgAdmin and PostgreSQL ports restricted to localhost
- Firebase credentials JSON stored securely (not in git)
- Mail credentials configured for password reset