Lab setup
How to start the local Docker LAN, connect to the learner host, and set up WireGuard for direct IP access.
Quick start
All lab hosts are defined in lab/compose.yml. Start them from the repository root:
git clone https://github.com/janschupke/nautilus cd lab docker compose up -d
Then SSH in via the published port on your local machine:
ssh -p 2222 isopod@127.0.0.1
Credentials — user: isopod / password: isopod. Exercise fixtures are seeded under /home/isopod/labs inside the container.
Network architecture
Containers share a bridge network at 10.78.8.0/24. Each host has a fixed IP and a role:
| IP | Host | Purpose |
|---|---|---|
| 10.78.8.10 | client | SSH entry point; learner host with exercise fixtures and Apache logs |
| 10.78.8.11 | lan-client | Simulates background user traffic (cron HTTP requests to webserver every 5 min) |
| 10.78.8.20 | webserver | PHP site, REST API, login, PostgreSQL integration |
| 10.78.8.30 | db | PostgreSQL — database for webserver exercises |
| 10.78.8.40 | exchange-ftp | FTP service for file-transfer exercises |
| 10.78.8.41 | exchange-smtp | SMTP / Mailpit — email exercises; web UI at http://127.0.0.1:8025 |
| 10.78.8.50 | forensics-box | Forensics analysis host for incident response and triage exercises |
| 10.78.8.2 | vpn | WireGuard gateway — routes your machine into the 10.78.8.0/24 LAN |
Ports published to your host machine: 2222→22 (SSH), 8000→80 (client Apache), 8080→80 (webserver), 8025→8025 (Mailpit), and 51820/udp (WireGuard).
WireGuard — direct LAN access
On Linux the Docker bridge is directly reachable from the host, so you can ssh isopod@10.78.8.10 without WireGuard. On macOS and Windows Docker Desktop runs containers inside a hidden VM, so the 10.78.8.0/24 subnet is not accessible from your machine by default. The vpn container bridges that gap via WireGuard.
1 — Extract the peer config
After docker compose up -d, the WireGuard container generates a peer config for the isopod peer and writes it to a named volume. Print it:
docker compose exec vpn cat /config/peer_isopod/peer_isopod.conf
Or copy the file to your working directory:
docker compose cp vpn:/config/peer_isopod/peer_isopod.conf ./isopod.conf
The config contains a private key, the server endpoint, and the allowed IPs route (10.78.8.0/24). Keep it local — it grants LAN access.
2 — Import and activate
Open the WireGuard app (macOS: App Store; Windows: wireguard.com). Click Import tunnel from file and select isopod.conf, or use the CLI:
# macOS (after installing wireguard-tools via Homebrew) wg-quick up ./isopod.conf # Windows (admin PowerShell) & "C:\Program Files\WireGuard\wireguard.exe" /installtunnelservice isopod.conf
Activate the tunnel in the app (toggle it on), or the wg-quick up command does it immediately. You only need to do this once per session — the tunnel stays up until you deactivate it.
3 — Verify connectivity
With the tunnel active, the full LAN is reachable by IP:
ping 10.78.8.10 ssh isopod@10.78.8.10 # no port flag needed curl http://10.78.8.20/ # webserver curl http://10.78.8.20/api/ # REST API
Re-generating the config
If you rebuild the lab with docker compose down -v the WireGuard volume is wiped and a new key pair is generated on the next up. You will need to re-extract and re-import the config.
Troubleshooting SSH on port 2222
ssh -p 2222 isopod@127.0.0.1 refusing the connection even when the containers appear to be running is one of the most common setup problems. Work through these checks in order.
1 — Confirm the container is healthy
docker compose ps
The client service must show running or Up, not restarting or exited. If it is not running, read the logs:
docker compose logs client
2 — Check what is actually listening on port 2222
The container may be running but the port binding may have failed silently (e.g. another process already held the port at compose-up time).
# macOS / Linux lsof -i :2222 # Linux alternative ss -tlnp | grep 2222 # Quick reachability test (exits 0 if something answers) nc -zv 127.0.0.1 2222
If Docker (or com.docker) owns port 2222, the binding is correct — the problem is inside the container; continue to step 3. If a different process owns it, that process grabbed the port before Docker could — see step 4. If nothing appears, Docker never bound the port at all; re-check step 1 and look at docker compose logs client for startup errors.
3 — Wait for sshd to finish starting
docker compose up -d returns as soon as the container process starts, not after the init system inside has finished launching sshd. On a cold build this can take a few seconds. Run:
docker compose exec client ps aux | grep sshd
If sshd is absent, wait five seconds and try again. If it never appears, the Dockerfile entrypoint may have failed:
docker compose logs --tail 50 client
4 — Port conflict — another process grabbed 2222 first
Find and kill the conflicting process, then re-create the container so Docker can bind the port:
# Find the PID holding port 2222 lsof -ti :2222 # Kill it (replace <PID>) kill <PID> # Re-create the client container docker compose up -d --force-recreate client
Common culprits: a previous container that was not fully stopped, a local SSH proxy, or another lab stack. Running docker compose down before up -d avoids this.
5 — Stale SSH host-key warning
If you previously connected and then rebuilt the lab volumes, the host key changes and SSH refuses with a REMOTE HOST IDENTIFICATION HAS CHANGED error. Clear the stale entry:
ssh-keygen -R "[127.0.0.1]:2222"
Then reconnect — SSH will prompt you to accept the new fingerprint.
6 — macOS / Windows: Docker Desktop not running
Docker Desktop must be running (macOS menu bar / Windows system tray) for port forwarding to work. If you started the containers in a previous session and Docker Desktop was restarted since then, the port mappings may have been dropped. Bring everything back up:
docker compose down docker compose up -d
Published services
These ports are forwarded to 127.0.0.1 on your host while the lab is running:
ssh -p 2222 isopod@127.0.0.1— learner shellcurl http://127.0.0.1:8000/— client Apachecurl http://127.0.0.1:8080/— webserver APIhttp://127.0.0.1:8025/— Mailpit web UI51820/udp— WireGuard (see above)
Teardown
Stop and remove all containers without deleting data volumes:
docker compose down
Full reset — stop containers and wipe all volumes (database, WireGuard keys, exercise data):
docker compose down -v
After down -v the next up -d rebuilds from scratch: fresh database seed, new WireGuard key pair (re-import the peer config), and clean exercise fixtures.