This guide walks through how to deploy rustunnel-server with Docker, in two scenarios:Documentation Index
Fetch the complete documentation index at: https://rustunnel.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
- Scenario A — Local development: run the server on your laptop using a self-signed certificate.
- Scenario B — VPS production: deploy on a cloud server with a real domain and Let’s Encrypt TLS.
Prerequisites
| Tool | Version | Notes |
|---|---|---|
| Docker Engine | 24+ | Install docs |
| Docker Compose | v2 (plugin) | Bundled with Docker Desktop; docker compose (no hyphen) |
| Git | any | To clone the repository |
How the Docker image works
Thedeploy/Dockerfile is a three-stage build:
ui-builder— installs Node 20, runsnext buildon the dashboard UI, and produces the static export underout/.builder— compiles the Rust server. Theout/directory is copied intocrates/rustunnel-server/src/dashboard/assets/sorust-embedcan bake it into the binary at compile time.runtime— minimaldebian:bookworm-slimimage containing only the server binary andca-certificates.
Because both the UI and the Rust binary are built inside Docker you do not need Node.js or Rust installed on the host to build or run the image.
Scenario A — Local development
Use this when you want to run a full server stack on your laptop for testing or development.1 — Generate a self-signed certificate
/certs.
To use a different path, set the CERT_DIR environment variable before running compose.
2 — Build the image
Cargo.lock or package-lock.json change.
3 — Start the server
-d to detach:
4 — Verify it is running
5 — Connect a client
6 — Stop the server
Port reference (local)
| Port | Purpose |
|---|---|
4040 | Control-plane WebSocket — clients connect here |
4041 | Dashboard UI and REST API |
8080 | HTTP edge (tunnel ingress, redirects to HTTPS) |
8443 | HTTPS edge (TLS-terminated tunnel ingress) |
20000–20099 | TCP tunnel range |
Reaching HTTP tunnel URLs locally
HTTP tunnels use subdomains (e.g.http://abc123.localhost:8080).
Browsers do not resolve *.localhost by default. Two options:
Option A — curl with a Host header (no setup)
http://abc123.localhost:8080 in the browser.
Scenario B — VPS production
Use this when you have a cloud server (Ubuntu 22.04 or later recommended) with a public IP address.Assumptions
| Item | Example value |
|---|---|
| Domain | edge.rustunnel.com |
| Wildcard DNS | *.edge.rustunnel.com → <server public IP> |
| TLS | Let’s Encrypt via Certbot + Cloudflare DNS challenge |
| OS | Ubuntu 22.04 LTS |
| PostgreSQL | Managed instance or self-hosted (required — see below) |
PostgreSQL is required. The server uses PostgreSQL to store API tokens and the tunnel audit log. You may run PostgreSQL on the same VPS or use a managed service (e.g. Supabase, Neon, AWS RDS, DigitalOcean Managed Databases). Set the connection URL in
deploy/server.toml under [database] url.edge.rustunnel.com (bare) and *.edge.rustunnel.com (wildcard) must resolve to your server IP — the wildcard is required so HTTP tunnel subdomains work.
1 — Install dependencies on the VPS
2 — Clone the repository
3 — Obtain TLS certificates
Create the Cloudflare credentials file:4 — Configure the server
Generate a strong admin token:deploy/server.toml — set at minimum:
5 — Grant the container access to the certificates
The container runs as a non-root user (rustunnel). Certbot sets restrictive permissions on the live/ and archive/ directories by default:
6 — Build the Docker image
7 — Start the server
docker-compose.yml mounts:
./server.toml→/etc/rustunnel/server.toml(read-only)/etc/letsencryptis not mounted by default — add the line below to the server service’svolumessection before starting:
8 — Open firewall ports
Port 9090 only needs to be open if you have an external Prometheus scraper. It is safe to leave it closed if you are running Prometheus on the same host (it reaches the metrics endpoint over the Docker bridge network).
9 — Verify the deployment
10 — Connect a client
Port reference (production)
| Port | Purpose |
|---|---|
80 | HTTP edge (redirects to HTTPS; handles ACME HTTP-01 if enabled) |
443 | HTTPS edge (TLS-terminated tunnel ingress) |
4040 | Control-plane WebSocket — clients connect here |
8443 | Dashboard UI and REST API |
9090 | Prometheus metrics (/metrics) |
20000–20099 | TCP tunnel range (configurable via tcp_port_range) |
Optional: monitoring stack (Prometheus + Grafana)
Both compose files expose the metrics endpoint to therustunnel Docker network.
The Prometheus service in docker-compose.yml scrapes it automatically.
Useful make targets
| Target | Description |
|---|---|
make docker-build | Build the Docker image |
make docker-run | Start the server container only |
make docker-run-monitoring | Start server + Prometheus + Grafana |
make docker-logs | Tail server container logs |
make docker-stop | Stop and remove all containers |
Updating
Pull the latest code and rebuild:--force-recreate flag restarts the container with the new image while
leaving the rustunnel-data volume (SQLite captured-request data) intact.
PostgreSQL data lives outside the container and is unaffected by container
updates.
Troubleshooting
Container exits immediately
- Config not mounted — ensure
deploy/server.tomlexists and the volume path is correct. - Cert files not readable — check permissions on
/etc/letsencrypt/(see step 5). - Port already in use — check
ss -tlnp | grep -E '80|443|4040|8443'.
Dashboard shows “dashboard assets not found”
The dashboard assets were not embedded at compile time. This happens if you built the Rust binary before runningnpm run build (or outside Docker). Rebuild the image with docker build — the multi-stage Dockerfile handles the UI build automatically.
--insecure flag required even in production
This means the client is connecting to a server with a self-signed cert. Verify that the cert paths in server.toml point to the Let’s Encrypt PEM files and that those files are accessible inside the container.
Prometheus shows no data
Check thatdeploy/prometheus.yml targets rustunnel-server:9090 and that both services are on the same Docker network (rustunnel). The metrics endpoint is not exposed on the host by default — Prometheus reaches it over the bridge network.
