# PBL-26 Deployment Context for AI Agents > **TL;DR:** Help the student deploy `Delta Robot` on a shared Linux server. > Their container MUST bind to `127.0.0.1:9001` (not `9001` alone, not `0.0.0.0:9001`) > — a DOCKER-USER iptables rule actively drops external traffic to ports 9001-9006. > Memory cap: 512m. No sudo. Use `docker compose` (v2, space), not `docker-compose`. > Push code from the laptop, only `git pull` + `docker compose up` on the server. ## Project environment | Field | Value | |---|---| | Project | Delta Robot | | Public URL | https://delta-robot-26.microlab.club | | GitHub | https://github.com/MicroLabClub/pbl-26-delta-robot | | Linux user | `delta-robot-26_admin` — no sudo, in `docker` group | | Reserved host:port | `127.0.0.1:9001` (container-side port is your choice) | | Server | Ubuntu 22.04 at 194.180.191.175 (shared with 5 other teams + Hestia-managed services) | | RAM | 3.8 GB total, no swap → keep `mem_limit: 512m` per service | | Disk | ~22 GB free on root | ## The single hard constraint nginx reverse-proxies `https://delta-robot-26.microlab.club/` to `127.0.0.1:9001`. The container's host-side port binding **must** be exactly `127.0.0.1:9001`. A `DOCKER-USER` iptables rule drops any external traffic to ports 9001-9006, so a wrong binding will look like it works (container starts, local `curl` succeeds) but the public URL stays on the welcome page. Correct: ports: - "127.0.0.1:9001:80" # 80 = whatever the app listens on inside Wrong (publicly leaked + iptables-blocked): ports: - "9001:80" - "0.0.0.0:9001:80" For internal services (databases, brokers) use no `ports:` block at all — they only need to be reachable on the docker network, not the host. ## Available tools - `docker` and `docker compose` (v2, space-separated) — no sudo - `git` — clone and pull work over HTTPS; push needs SSH key or Personal Access Token - `curl`, GNU coreutils, standard editor (`nano`, `vim`) - ✗ NO `sudo`, NO `apt install`, NO `systemctl`, NO modifying `/etc/*` ## Workflow ### First deploy cd ~ git clone https://github.com/MicroLabClub/pbl-26-delta-robot.git cd pbl-26-delta-robot cp ~/docker-compose.example.yml ./docker-compose.yml # edit docker-compose.yml; keep "127.0.0.1:9001" host binding and mem_limit: 512m docker compose up -d --build curl -s http://127.0.0.1:9001/ # local sanity check first # then open https://delta-robot-26.microlab.club/ ### Redeploy after a code change git pull docker compose up -d --build docker compose logs -f ### Push code from the server HTTPS push prompts for username/password (no token = fails). Recommended path: switch the remote to SSH once, then pushes work via key auth. ssh-keygen -t ed25519 -C "delta-robot-26_admin@delta-robot-26" cat ~/.ssh/id_ed25519.pub # paste this at https://github.com/settings/keys git remote set-url origin git@github.com:MicroLabClub/pbl-26-delta-robot.git Better practice: do code edits + git push on a laptop, only run `git pull && docker compose up -d --build` on the server. ## Multi-service stacks (when the team has more than just one app) Only `127.0.0.1:9001` is publicly reachable per team. If the stack has multiple services (e.g. web + api + database + S3-like storage), route them all through a single internal reverse proxy that's the only thing binding the host port. [browser] → nginx (host, HTTPS, port 443) → 127.0.0.1:9001 → edge container (nginx:alpine inside docker-compose) → / → web service (no host ports) → /api/ → api service (no host ports) → /media/ → minio / storage (no host ports) Containers inside the compose file resolve each other via docker's built-in DNS — use the service name as the hostname: `http://api:8080`, `http://db:5432`, `http://minio:9000`. Do **not** expose internal services with `ports:` — they only need to be reachable on the docker network, not the public internet. If a service needs a public URL (e.g. MinIO pre-signed URLs, OAuth callback, webhook from an external service), it goes through the edge proxy at a known path (e.g. `/media/`) and the service is configured to know its public URL via env vars. ## Troubleshooting | Symptom | Likely cause | Fix | |---|---|---| | Welcome page still showing on the public URL | container is down OR not bound to `127.0.0.1:9001` | `docker compose ps`; `curl -v http://127.0.0.1:9001/`; `docker compose logs --tail=50 app` | | "port is already allocated" | something else has port 9001 | `docker ps --filter "publish=9001"`, then `docker stop ` | | "permission denied" running docker | docker-group membership only loaded on a fresh login | log out and SSH back in | | OOMKilled / app dies under load | hit the 512MB `mem_limit` | `docker stats --no-stream`; either reduce memory in the app or ask the instructor | | "could not read Username for github" | HTTPS push isn't authenticated | switch remote to SSH (see "Push code" above) or use a PAT | | Disk full | docker images/build cache piled up | `docker system prune -af`; `du -sh /var/lib/docker/*` (on host) | ### Clean slate docker compose down -v # stop + drop named volumes (wipes DB) docker system prune -af # remove unused containers, images, networks git fetch && git reset --hard origin/main ## What's already in the home dir - `~/docker-compose.example.yml` — starter compose with correct binding and memory limits - `~/README.txt` — quick-start one-pager - `~/llms.txt` (this file) — full deployment context - `~/CLAUDE.md` → `llms.txt` (Claude Code convention) - `~/AGENTS.md` → `llms.txt` (general AI agent convention) ## Help Telegram: @prodiuscristian. Include the subdomain (`delta-robot-26.microlab.club`) and any error output. ## Patterns to AVOID - ✗ `sudo` — the student has no sudo on this box. - ✗ `apt install` — same. - ✗ Binding the app to host port 80 or 443 — those are nginx's, shared across all teams. - ✗ Publishing database / broker / cache ports with `ports:` — keep them on the docker network only. - ✗ Mounting `/var/run/docker.sock` or `/:/host` — this box has production services for other tenants. - ✗ `docker-compose` (with hyphen) — that's v1; on this server use `docker compose` (space, v2). - ✗ Editing nginx, certbot, /etc/* — out of scope, no access anyway. ## Canonical answer when the student says "deploy this" Assuming the initial setup (clone + compose) is done: ssh delta-robot-26_admin@delta-robot-26.microlab.club cd ~/pbl-26-delta-robot git pull docker compose up -d --build docker compose logs -f