Server Deployment
Learn how to deploy the Portr admin dashboard and tunnel server with Docker Compose to self-host your own tunneling infrastructure.
For quick setup, use the production compose file from the Portr repository:
https://github.com/amalshaji/portr/blob/main/docker-compose.prod.yaml
Services Overview
The production compose file includes 3 services:
- caddy: The reverse proxy for routing and SSL termination
- server: The combined admin and tunnel server
- postgres: The PostgreSQL database
Deployment Steps
Download the compose file
Download docker-compose.prod.yaml from the repository and place it in your server directory.
Set up environment variables
Create a .env file in the same directory with the following values:
PORTR_ADMIN_GITHUB_CLIENT_ID= #optional
PORTR_ADMIN_GITHUB_CLIENT_SECRET= #optional
PORTR_DOMAIN=example.com
PORTR_DB_URL=postgres://postgres:<choose-a-strong-secret>@postgres:5432/postgres
PORTR_SERVER_URL=https://example.com
PORTR_SSH_URL=example.com:2222
PORTR_AUTO_MIGRATE=true
PORTR_RESERVED_SUBDOMAIN_LIMIT=3
PORTR_SSH_HOST_KEY= # PEM-encoded Ed25519 private key
CLOUDFLARE_API_TOKEN=
POSTGRES_USER=postgres
POSTGRES_PASSWORD=<choose-a-strong-secret>
POSTGRES_DB=postgresGenerate unique credentials for each deployment. Replace every placeholder in
the .env file, and use the same database secret in POSTGRES_PASSWORD and
PORTR_DB_URL.
Environment Variable Reference
| Variable | Description | Default |
|---|---|---|
PORTR_DOMAIN | Your server domain | localhost:8001 |
PORTR_DB_URL | PostgreSQL connection URL | Required |
PORTR_SERVER_URL | Public server URL for clients | http://localhost:8001 |
PORTR_SSH_URL | Public SSH URL for clients | localhost:2222 |
PORTR_SSH_PORT | SSH server port | 2222 |
PORTR_SSH_HOST_KEY | PEM-encoded Ed25519 private key | Required |
PORTR_ADMIN_PORT | Admin server port | 8000 |
PORTR_ADMIN_GITHUB_CLIENT_ID | GitHub OAuth client ID | Optional |
PORTR_ADMIN_GITHUB_CLIENT_SECRET | GitHub OAuth client secret | Optional |
PORTR_RESERVED_SUBDOMAIN_LIMIT | Maximum reserved subdomains per team membership; use 0 to disable new reservations | 3 |
PORTR_AUTO_MIGRATE | Auto-run database migrations | false |
CLOUDFLARE_API_TOKEN | Cloudflare API token for SSL | Required |
Generate SSH Host Key
Generate an Ed25519 private key and set it as an environment variable:
ssh-keygen -t ed25519 -f /tmp/portr_host_key -N ""
export PORTR_SSH_HOST_KEY="$(cat /tmp/portr_host_key)"
rm /tmp/portr_host_key /tmp/portr_host_key.pubOr use portrd to generate and print a key:
portrd generate-host-keyFor Docker deployments, you can pass the key as a multi-line environment variable in your .env file or use Docker secrets.
Start the services
Run the following command to start all services:
docker compose -f docker-compose.prod.yaml up -dThe server container runs as a non-root user. If you keep the default ./data
bind mount, make sure that directory is writable by the container user before
starting the service.
Access the admin dashboard
Once the servers are running, navigate to your domain (e.g., https://example.com) and log in to the admin dashboard. The first login will be treated as a superuser account.
Alternative Database Setup
If you want to run PostgreSQL separately and not as a Docker service, you can exclude the following environment variables from your .env file:
POSTGRES_USERPOSTGRES_PASSWORDPOSTGRES_DB
Troubleshooting
Service Health Checks
Check if all services are running properly:
docker compose -f docker-compose.prod.yaml psView Service Logs
Check logs for any specific service:
# View all logs
docker compose -f docker-compose.prod.yaml logs
# View logs for a specific service
docker compose -f docker-compose.prod.yaml logs server
docker compose -f docker-compose.prod.yaml logs caddy
docker compose -f docker-compose.prod.yaml logs postgresSSL Certificate Issues
If you're having SSL certificate issues, ensure:
- Your DNS records are properly configured
- Your Cloudflare API token has the correct permissions
- Your domain is added to Cloudflare
Port Accessibility
Verify that the required ports are accessible:
# Check if port 2222 is accessible
telnet your-domain.com 2222
# Check if HTTP/HTTPS ports are working
curl https://your-domain.com