5.1 KiB
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:
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:
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):
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:
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:
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_PASSWORDandSPPM_DB_*to match step 1b.APP_KEYis pre-filled; to rotate it rundocker compose run --rm app php artisan key:generate --showand paste the result.
DB_HOST / SPPM_DB_HOST are already set to host.docker.internal — leave them.
3. Build and start
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:
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
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
# 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 ofpublic/. Files written at runtime tostorage/app/public(thepublicdisk +storage:link) live only in theappcontainer 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, switchwebto share the app's public/storage via a volume. - Persistent data: the named volume
app_storagekeeps logs, compiled views, andstorage/appacross 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 addSESSION_SECURE_COOKIE=trueandAPP_URL=https://...in.env.docker. - HEALTHCHECK / scheduler: there are currently no scheduled commands, but
the
schedulerservice is included so futureapp/Consoleschedules work without changing the stack. Remove it if you prefer.