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)
This commit is contained in:
@@ -74,18 +74,6 @@ services:
|
|||||||
- tckdev_net
|
- tckdev_net
|
||||||
restart: unless-stopped
|
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:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
pgadmin_data:
|
pgadmin_data:
|
||||||
|
|||||||
@@ -1,122 +1,251 @@
|
|||||||
# Deployment & Infrastructure
|
# 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
|
## Prerequisites
|
||||||
|
|
||||||
- Docker ^19.*
|
- Docker Engine ^20.x
|
||||||
- Docker Compose
|
- Docker Compose v2+
|
||||||
- Make (optional, for Makefile commands)
|
- A reverse proxy in front (Nginx, Caddy, Traefik, Cloudflare Tunnel, etc.) for TLS termination
|
||||||
|
|
||||||
## Environment Configuration
|
## Environment Configuration
|
||||||
|
|
||||||
### Root `.env` (Docker-level)
|
Two `.env` files control the stack:
|
||||||
|
|
||||||
Located at `/root/sides/tckdev/.env`:
|
### Root `.env` — Docker Compose variables
|
||||||
|
|
||||||
```
|
Located at the project root. Controls container-level settings and credential defaults.
|
||||||
POSTGRES_DB="tckdev"
|
|
||||||
|
```env
|
||||||
|
POSTGRES_DB="sides_db"
|
||||||
POSTGRES_USER="tck"
|
POSTGRES_USER="tck"
|
||||||
POSTGRES_PASSWORD="projectdev##1"
|
POSTGRES_PASSWORD="<your_strong_password>"
|
||||||
PGADMIN_EMAIL="tck68000@gmail.com"
|
|
||||||
PGADMIN_PASSWORD="projectdev##1"
|
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-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:
|
Key settings:
|
||||||
- `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`
|
|
||||||
|
|
||||||
## 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
|
```bash
|
||||||
make create-project
|
# 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
|
||||||
```
|
```
|
||||||
|
|
||||||
This runs:
|
The `app` container's entrypoint automatically runs on first start:
|
||||||
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
|
|
||||||
|
|
||||||
### 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
|
```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:
|
## Reverse Proxy Setup
|
||||||
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`
|
|
||||||
|
|
||||||
## 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 |
|
The nginx container does **not** handle HTTPS itself — it expects plain HTTP from the reverse proxy. Configure your reverse proxy to:
|
||||||
|---------|-------------|
|
|
||||||
| `make up` | Start containers (detached) |
|
1. Listen on port **443** with your TLS certificate
|
||||||
| `make down` | Stop containers, remove orphans |
|
2. Forward to `localhost:80` (or the Docker host's port 80)
|
||||||
| `make down-v` | Stop containers, remove volumes |
|
3. Set the `X-Forwarded-Proto` header so Laravel generates `https://` URLs
|
||||||
| `make build` | Build Docker images |
|
4. Set the `X-Forwarded-Host` header with the original hostname
|
||||||
| `make remake` | Full destroy + reinit |
|
|
||||||
| `make destroy` | Remove all containers, images, volumes |
|
### Example: Nginx Reverse Proxy
|
||||||
| `make stop` | Stop containers |
|
|
||||||
| `make restart` | Down + up |
|
On the host machine running Docker, add a server block:
|
||||||
| `make ps` | Show container status |
|
|
||||||
| `make logs` | Show all container logs |
|
```nginx
|
||||||
| `make logs-watch` | Follow all logs |
|
server {
|
||||||
| `make log-app` | Show app container logs |
|
listen 443 ssl http2;
|
||||||
| `make log-db` | Show database logs |
|
server_name sides.tck.com.my;
|
||||||
| `make app` | Shell into app container |
|
|
||||||
| `make web` | Shell into web container |
|
ssl_certificate /etc/ssl/certs/sides.tck.com.my.crt;
|
||||||
| `make migrate` | Run migrations |
|
ssl_certificate_key /etc/ssl/private/sides.tck.com.my.key;
|
||||||
| `make fresh` | Fresh migration with seed |
|
|
||||||
| `make seed` | Run seeders |
|
location / {
|
||||||
| `make tinker` | Open Laravel tinker |
|
proxy_pass http://127.0.0.1:80;
|
||||||
| `make test` | Run PHPUnit tests |
|
proxy_set_header Host $host;
|
||||||
| `make cache` | Optimize autoload + cache |
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
| `make cache-clear` | Clear all caches |
|
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
|
## Access Points
|
||||||
|
|
||||||
| Service | URL |
|
| Service | Internal | External (via reverse proxy) |
|
||||||
|---------|-----|
|
|---------|----------|------------------------------|
|
||||||
| Application | http://localhost:80 |
|
| Application | `web:80` | `https://sides.tck.com.my` |
|
||||||
| pgAdmin | http://localhost:5050 |
|
| pgAdmin | `pgadmin:80` | `http://<host>:5050` |
|
||||||
| Adminer | http://localhost:6060 |
|
| PostgreSQL | `postgres:5432` | `localhost:5432` (optional, for external tools) |
|
||||||
| PostgreSQL | localhost:5432 |
|
|
||||||
|
|
||||||
## 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
|
## Data Persistence
|
||||||
- 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
|
|
||||||
|
|
||||||
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
|
The `src/` directory is bind-mounted into both `app` and `web` containers at `/var/www/html`.
|
||||||
- `URL::forceScheme('https')` is enabled globally in `AppServiceProvider`
|
|
||||||
- The app URL is `https://sides.tck.com.my`
|
## Autoscript Deployment
|
||||||
- Gmail SMTP is used for password reset emails
|
|
||||||
- Firebase credentials JSON file is stored in `storage/app/firebase/`
|
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=<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=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
|
||||||
|
|||||||
Reference in New Issue
Block a user