From c1b2a8d55379a4b7fe98859b946438d7c53e5079 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 21 May 2026 02:46:41 +0800 Subject: [PATCH] docs: rewrite deployment guide for Docker-only, add reverse proxy section - 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) --- docker-compose.yml | 12 -- docs/03-DEPLOYMENT.md | 301 ++++++++++++++++++++++++++++++------------ 2 files changed, 215 insertions(+), 98 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0ce10b67..3601ca8e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,18 +74,6 @@ services: - tckdev_net restart: unless-stopped - adminer: - container_name: tckdev-adminer - image: adminer - restart: always - ports: - - "6060:8080" - depends_on: - postgres: - condition: service_healthy - networks: - - tckdev_net - volumes: pgdata: pgadmin_data: diff --git a/docs/03-DEPLOYMENT.md b/docs/03-DEPLOYMENT.md index 8e410595..30cbe109 100644 --- a/docs/03-DEPLOYMENT.md +++ b/docs/03-DEPLOYMENT.md @@ -1,122 +1,251 @@ # 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 ^19.* -- Docker Compose -- Make (optional, for Makefile commands) +- Docker Engine ^20.x +- Docker Compose v2+ +- A reverse proxy in front (Nginx, Caddy, Traefik, Cloudflare Tunnel, etc.) for TLS termination ## Environment Configuration -### Root `.env` (Docker-level) +Two `.env` files control the stack: -Located at `/root/sides/tckdev/.env`: +### Root `.env` — Docker Compose variables -``` -POSTGRES_DB="tckdev" +Located at the project root. Controls container-level settings and credential defaults. + +```env +POSTGRES_DB="sides_db" POSTGRES_USER="tck" -POSTGRES_PASSWORD="projectdev##1" -PGADMIN_EMAIL="tck68000@gmail.com" -PGADMIN_PASSWORD="projectdev##1" +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-level) +### Application `.env` — Laravel variables -Located at `/root/sides/tckdev/src/.env`: +Located at `src/.env`. This file references Docker service names (e.g. `DB_HOST=postgres`) so Laravel connects to containers, not external hosts. -Key configuration: -- `APP_URL=https://sides.tck.com.my` -- `DB_CONNECTION=pgsql` → `DB_HOST=192.168.0.211` (direct IP, not Docker service name) -- `SESSION_DRIVER=database` -- `CACHE_STORE=database` -- `QUEUE_CONNECTION=database` -- `MAIL_MAILER=smtp` via Gmail SMTP (`sideskupang@gmail.com`) -- Firebase FCM credentials at `storage/app/firebase/sides-b4abb-3604a7cf7584.json` +Key settings: -## Initial Setup +```env +APP_NAME=SIDES +APP_ENV=production +APP_DEBUG=false +APP_URL=https://sides.tck.com.my -### From Scratch (New Project) +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 -make create-project +# 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 ``` -This runs: -1. Creates `src/` directory -2. Builds Docker images -3. Starts containers -4. Runs `composer create-project laravel/laravel .` -5. Generates application key -6. Creates storage link -7. Sets permissions -8. Installs npm dependencies +The `app` container's entrypoint automatically runs on first start: -### From Existing Code (Clone) +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 -make init +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 ``` -This runs: -1. Builds and starts containers -2. `composer install` -3. Copies `.env.example` to `.env` -4. Generates application key -5. Creates storage link -6. Sets permissions -7. `npm install` -8. `php artisan migrate:fresh --seed` +## Reverse Proxy Setup -## Makefile Commands Reference +The `web` container (nginx) listens on port **80 internally**. A reverse proxy in front handles TLS termination and forwards traffic to it. -| Command | Description | -|---------|-------------| -| `make up` | Start containers (detached) | -| `make down` | Stop containers, remove orphans | -| `make down-v` | Stop containers, remove volumes | -| `make build` | Build Docker images | -| `make remake` | Full destroy + reinit | -| `make destroy` | Remove all containers, images, volumes | -| `make stop` | Stop containers | -| `make restart` | Down + up | -| `make ps` | Show container status | -| `make logs` | Show all container logs | -| `make logs-watch` | Follow all logs | -| `make log-app` | Show app container logs | -| `make log-db` | Show database logs | -| `make app` | Shell into app container | -| `make web` | Shell into web container | -| `make migrate` | Run migrations | -| `make fresh` | Fresh migration with seed | -| `make seed` | Run seeders | -| `make tinker` | Open Laravel tinker | -| `make test` | Run PHPUnit tests | -| `make cache` | Optimize autoload + cache | -| `make cache-clear` | Clear all caches | +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 | URL | -|---------|-----| -| Application | http://localhost:80 | -| pgAdmin | http://localhost:5050 | -| Adminer | http://localhost:6060 | -| PostgreSQL | localhost:5432 | +| 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) | -## Database Connection Discrepancy +**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`: -**Important**: The `docker-compose.yml` defines a `postgres` service, but `src/.env` connects to `192.168.0.211` — an external PostgreSQL host, not the Docker container. This means: +```yaml +# docker-compose.yml — restrict to localhost only +ports: + - "127.0.0.1:5050:80" # pgAdmin + - "127.0.0.1:5432:5432" # PostgreSQL +``` -- In **development/local**: The app may connect to an external database instead of the Docker postgres container -- The Docker postgres service is still available at `postgres:5432` for inter-container communication -- The Python autoscript also connects to `192.168.0.211` directly +## Data Persistence -To use the Docker PostgreSQL, change `DB_HOST=postgres` in `src/.env`. +Two named volumes store persistent data: -## Production Notes +| Volume | Mount Point | Purpose | +|--------|------------|---------| +| `pgdata` | `/var/lib/postgresql/data` | PostgreSQL data | +| `pgadmin_data` | `/var/lib/pgadmin` | pgAdmin sessions and config | -- `APP_DEBUG=true` is set in `.env` — should be `false` in production -- `URL::forceScheme('https')` is enabled globally in `AppServiceProvider` -- The app URL is `https://sides.tck.com.my` -- Gmail SMTP is used for password reset emails -- Firebase credentials JSON file is stored in `storage/app/firebase/` +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