Files
2026-06-14 09:17:46 +08:00

162 lines
5.1 KiB
Markdown

# 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_taklimat` and the external `sppm` database live).
```
Internet
host nginx (taklimatspr.apps.mbip.my, port 80) ← reverse proxy
│ proxy_pass 127.0.0.1:8080
web container (nginx) ──fastcgi──► app container (php-fpm 8.4)
▼ host.docker.internal:3306
host MySQL (sppm_taklimat, sppm)
```
Containers:
| Service | Image | Role |
|-------------|--------------------|-------------------------------------------------|
| `web` | `taklimatspr-web` | nginx, serves static assets, proxies PHP. Binds `127.0.0.1:8080`. |
| `app` | `taklimatspr-app` | PHP 8.4-FPM. Runs migrations on boot. |
| `queue` | `taklimatspr-app` | `queue:work` (database queue). |
| `scheduler` | `taklimatspr-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 'taklimat'@'172.%' IDENTIFIED BY 'CHANGE_ME_STRONG';
GRANT ALL PRIVILEGES ON sppm_taklimat.* TO 'taklimat'@'172.%';
GRANT SELECT ON sppm.* TO 'taklimat'@'172.%';
FLUSH PRIVILEGES;
```
Make sure the `sppm_taklimat` database exists:
```sql
CREATE DATABASE IF NOT EXISTS sppm_taklimat
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:8080 # should return HTTP 200/302
```
---
## 4. Point host Nginx at the stack
```bash
sudo cp docker/host-nginx/taklimatspr.apps.mbip.my.conf \
/etc/nginx/sites-available/taklimatspr.apps.mbip.my
sudo ln -s /etc/nginx/sites-available/taklimatspr.apps.mbip.my \
/etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
The app should now be reachable at <http://taklimatspr.apps.mbip.my>.
---
## 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.