Install with the Compose template
Use this path when you want an inspectable configuration that stays under normal Compose management. You do not need to run the PortLoom Server installer first: download two files, edit two values, and start the project.
Choose your own hostname
Set TM_PUBLIC_HOST to the complete management hostname you choose. It may be an apex domain such as example.com or any subdomain such as tunnel.example.com; there is no portloom. prefix requirement. example.com below is documentation-only, not a fixed value.
Where each component runs
Visitor
↓ your management/application hostname
Public VPS: PortLoom Server + WebUI + managed sshd
↑ NAS initiates the secure tunnel (no NAS inbound port)
Private NAS: PortLoom Agent → local services| Host | Install | Requirements |
|---|---|---|
| Public VPS | Server Compose project from this page | Docker Compose v2; public IPv4; TCP 80/443/2222 |
| NAS / internal server | Agent project generated by the WebUI | Docker; outbound HTTPS and SSH to the VPS |
One Server can manage multiple Agents. Start Server on the VPS first, then generate a separate Agent setup for each NAS in the WebUI.
1. Prepare DNS and ports
- Choose your own management hostname: either an apex domain such as
example.comor any subdomain such astunnel.example.com. - Point its A record to the VPS public IPv4 address.
- Allow public TCP
80,443, and2222, and make sure no other process owns local80/443. - The NAS needs neither a public IP nor an inbound port.
When you later publish application names such as jellyfin.example.com, either add a wildcard *.example.com record or create each A record separately.
An apex domain works. If it already serves a website, choose a separately named subdomain to avoid a conflict; the subdomain label is entirely your choice.
Are ports 80/443 already in use?
If the VPS already runs Caddy, Nginx, or Nginx Proxy Manager, do not start the default template. Read Production deployment and Reverse proxy integration first.
2. Download two files
Create a dedicated directory such as portloom/ on the VPS and put these files in it:
Rename compose.env.example to .env. The final directory is simply:
portloom/
├── compose.yml
└── .envYou can download with a browser, upload with a NAS/VPS file manager, and rename the file there. curl is not required.
3. Edit only two required values
Open .env:
TM_PUBLIC_HOST=
TM_ADMIN_TOKEN=TM_PUBLIC_HOST: enter the complete DNS hostname selected in step 1, such astunnel.example.com. Do not includehttps://, a port, or a path.TM_ADMIN_TOKEN: runopenssl rand -hex 32and paste the resulting 64 hexadecimal characters after the equals sign. This is the WebUI credential. A password manager may instead generate 64 characters using only0-9anda-f. Do not use$,#, quotes, or spaces; dotenv/Compose could reinterpret them.
Compose refuses to start while either value is empty, preventing deployment with a public placeholder credential.
Keep the remaining defaults initially. TM_TCP_EDGE_BIND_HOST=0.0.0.0 enables future TCP/UDP routes; set it to off when you only need web routes.
Keep .env private
.env contains the administrator token. Do not commit it, paste it into a public issue, or share screenshots of it.
4. Start the Compose project
NAS/server Compose UI
- Create a Compose/Project stack.
- Select the directory containing
compose.ymland.env. - Set the dedicated project directory to
0711(traversable but not listable), and make.envowner-readable/writable only (0600). Do not apply0711to an entire home directory. - Run its validate/parse action and confirm that no variable is missing.
- Select Create or Start.
Terminal
Run in the directory containing both files:
chmod 0711 .
chmod 0600 .env
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps -aExpected state:
portloom-server: running;portloom-sshd: running and healthy;state-init: exited successfully (it is a one-shot permission initializer, not a failure).
5. Open the WebUI
Visit:
https://the hostname set in TM_PUBLIC_HOSTEnter TM_ADMIN_TOKEN from .env. The first request may take tens of seconds while the certificate is issued. If the page does not open, check DNS, the VPS firewall, and local port ownership, then inspect:
docker compose logs --tail=100 server sshdAfter the page opens, verify the health endpoint:
curl --fail --head https://your-management-hostname/healthz6. Add the NAS Agent
Open Add Agent, enter an Agent name, Server URL, public SSH host, and port 2222, then select Generate command and paste the complete generated command on the NAS.
The generated Agent command intentionally remains: it safely handles the one-time enrollment token, Ed25519 key creation, Server SSH host-key pinning, and token removal after enrollment. A handwritten Agent Compose setup can easily omit those controls. Server installation and daily operation remain fully Compose-based through this page's compose.yml.
After the Agent appears in the WebUI, follow the five-minute quick start to create the first route.
Data, restarts, and upgrades
- The template pins the verified Server/sshd
0.4.1images and project-local./data/paths so shell variables cannot silently switch images or data sources. Advanced customization should be an explicit, reviewed edit tocompose.yml. - Keep the dedicated project directory at
0711so non-root containers can traverse NAS/FUSE paths, while the private.envremains0600. Do not apply these modes to an entire home directory. - Restart only the long-running services with
docker compose restart server sshd. After configuration or image changes, usedocker compose up -d; do not restart the one-shotstate-initby itself. - Do not recreate the stack with ad-hoc
docker run, and never delete the data directory during replacement. - Before upgrading, stop
serverandsshd, back up the entire project directory (compose.yml, private.env, and all ofdata/), and recorddocker compose images; then usedocker compose up -dto resume. Do not copy only a live SQLiteportloom.db. See Backup, upgrade, and rollback for the full procedure.
The automated path still exists when you prefer generated random credentials, immutable image resolution, readiness checks, and failed-upgrade rollback. See Secure installer. Advanced templates remain available under Template downloads.