# 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. ```env POSTGRES_DB="sides_db" POSTGRES_USER="tck" POSTGRES_PASSWORD="" PGADMIN_EMAIL="admin@example.com" 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="" FTP_SERVER="myvscada.com" FTP_USERNAME="tck" 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: ```env 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 ```bash # 1. Clone the repository git clone && 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: 1. `composer install` (if `vendor/` missing) 2. `npm install && npm run build` (if `public/build/` missing) 3. `php artisan migrate --force` (if `RUN_MIGRATIONS=true`) 4. `php artisan db:seed --force` (if `RUN_SEEDER=true`) 5. Caches config, routes, and views 6. Starts PHP-FPM ## Common Operations ```bash 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: 1. Listen on port **443** with your TLS certificate 2. Forward to `localhost:80` (or the Docker host's port 80) 3. Set the `X-Forwarded-Proto` header so Laravel generates `https://` URLs 4. Set the `X-Forwarded-Host` header with the original hostname ### Example: Nginx Reverse Proxy On the host machine running Docker, add a server block: ```nginx 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 ```bash 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://`: ```env APP_URL=https://sides.tck.com.my ``` If using a load balancer or multi-hop proxy, configure `TrustProxies` middleware: ```php // 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://: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`: ```yaml # 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): ```bash export PG_HOST=localhost export PG_PORT=5432 export PG_DATABASE=sides_db export PG_USER=tck export PG_PASSWORD= export FTP_SERVER=myvscada.com export FTP_USERNAME=tck export 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=production` and `APP_DEBUG=false` in `src/.env` - [ ] `APP_URL` uses `https://` - [ ] All passwords in `.env` changed from defaults - [ ] `ADMIN_PASSWORD` set 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