# Running with Docker This app is packaged as a small Docker stack and is designed to run **behind the host's existing Nginx**, connecting to the **host's existing MySQL** (where both `sppm_ktm` and the external `sppm` database live). ``` Internet │ ▼ host nginx (ktmspr.apps.mbip.my, port 80) ← reverse proxy │ proxy_pass 127.0.0.1:8009 ▼ web container (nginx) ──fastcgi──► app container (php-fpm 8.4) │ ▼ host.docker.internal:3306 host MySQL (sppm_ktm, sppm) ``` Containers: | Service | Image | Role | |-------------|--------------------|-------------------------------------------------| | `web` | `ktmspr-web` | nginx, serves static assets, proxies PHP. Binds `127.0.0.1:8009`. | | `app` | `ktmspr-app` | PHP 8.4-FPM. Runs migrations on boot. | | `queue` | `ktmspr-app` | `queue:work` (database queue). | | `scheduler` | `ktmspr-app` | `schedule:work` (Laravel scheduler). | --- ## Prerequisites on the host (Ubuntu 24.04) - Docker Engine + Compose plugin: ```bash sudo apt-get update sudo apt-get install -y docker.io docker-compose-v2 sudo systemctl enable --now docker ``` - The existing Nginx and MySQL services already installed on the host. --- ## 1. Configure host MySQL to accept connections from containers On Linux, `host.docker.internal` resolves to the Docker bridge gateway (e.g. `172.17.0.1`) via the `host-gateway` mapping already set in `docker-compose.yml`. The host MySQL must accept connections on that interface. **a) Listen on all interfaces** — edit `/etc/mysql/mysql.conf.d/mysqld.cnf`: ```ini bind-address = 0.0.0.0 ``` Then restart: `sudo systemctl restart mysql`. **b) Allow the app user from the Docker subnet.** Prefer a dedicated user over `root` (and then set the credentials in `.env.docker`): ```sql CREATE USER 'ktm'@'172.%' IDENTIFIED BY 'CHANGE_ME_STRONG'; GRANT ALL PRIVILEGES ON sppm_ktm.* TO 'ktm'@'172.%'; GRANT SELECT ON sppm.* TO 'ktm'@'172.%'; FLUSH PRIVILEGES; ``` Make sure the `sppm_ktm` database exists: ```sql CREATE DATABASE IF NOT EXISTS sppm_ktm CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` **c) Firewall** — if `ufw` is active, allow MySQL from the Docker subnet only: ```bash sudo ufw allow from 172.16.0.0/12 to any port 3306 proto tcp ``` --- ## 2. Set the container environment `.env.docker` holds the runtime config (it is git-ignored because it contains credentials). Review it and update at least: - `DB_USERNAME` / `DB_PASSWORD` and `SPPM_DB_*` to match step 1b. - `APP_KEY` is pre-filled; to rotate it run `docker compose run --rm app php artisan key:generate --show` and paste the result. `DB_HOST` / `SPPM_DB_HOST` are already set to `host.docker.internal` — leave them. --- ## 3. Build and start ```bash docker compose build docker compose up -d ``` On boot the `app` container waits for MySQL, caches config/routes/views, runs `php artisan migrate --force`, and starts php-fpm. Check it: ```bash docker compose ps docker compose logs -f app curl -I http://127.0.0.1:8009 # should return HTTP 200/302 ``` --- ## 4. Point host Nginx at the stack ```bash sudo cp docker/host-nginx/ktmspr.apps.mbip.my.conf \ /etc/nginx/sites-available/ktmspr.apps.mbip.my sudo ln -s /etc/nginx/sites-available/ktmspr.apps.mbip.my \ /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx ``` The app should now be reachable at . --- ## Common operations ```bash # Tail logs docker compose logs -f app web queue # Artisan / composer inside the app container docker compose exec app php artisan about docker compose exec app php artisan tinker # Re-cache config after editing .env.docker docker compose exec app php artisan config:cache # Rebuild & redeploy after pulling new code docker compose build && docker compose up -d ``` --- ## Notes & limitations - **Static uploads:** `web` (nginx) serves a baked copy of `public/`. Files written at runtime to `storage/app/public` (the `public` disk + `storage:link`) live only in the `app` container and are **not** served by nginx. The current app renders QR codes as inline SVG, so this isn't needed today — but if you later serve uploaded files via `/storage`, switch `web` to share the app's public/storage via a volume. - **Persistent data:** the named volume `app_storage` keeps logs, compiled views, and `storage/app` across restarts. Application/session data lives in MySQL. - **TLS:** terminate HTTPS at the host Nginx (e.g. with certbot) if/when the domain moves to `https://`. Then add `SESSION_SECURE_COOKIE=true` and `APP_URL=https://...` in `.env.docker`. - **HEALTHCHECK / scheduler:** there are currently no scheduled commands, but the `scheduler` service is included so future `app/Console` schedules work without changing the stack. Remove it if you prefer.