reference/relay-docker-compose.md
Relay Docker Compose (Portainer Copy Block)
Copy everything inside the code block into Portainer -> Stacks -> Add stack to deploy the relay.
# =============================================================================
# Mino Relay — Docker Compose (Portainer-friendly)
#
# Usage:
# docker compose -f docker/relay-docker-compose.yml up -d
#
# Copy-paste this file into Portainer → Stacks → "Add Stack" → Paste YAML
# to deploy the relay as a standalone stack.
#
# The relay enables private Mino servers to be accessible via mino.ink
# without requiring open inbound ports. Servers connect outbound to the
# relay, and the web client proxies requests through it.
#
# Cloudflare Tunnel (built-in):
# Set CF_TUNNEL_TOKEN in Portainer env vars to auto-expose the relay.
# The tunnel routes relay.mino.ink → relay:8787 internally.
# If CF_TUNNEL_TOKEN is not set, the tunnel sidecar stays idle.
#
# After deploying:
# 1. Set CF_TUNNEL_TOKEN to expose relay.mino.ink via tunnel.
# 2. Verify: GET https://relay.mino.ink/api/v1/health
# 3. Set NEXT_PUBLIC_RELAY_URL on Cloudflare Pages and redeploy.
# 4. Deploy the Mino server stack with MINO_RELAY_URL pointing here.
#
# See docs/relay.md for the full deployment guide.
# =============================================================================
services:
# ---------------------------------------------------------------------------
# Mino Relay — WebSocket proxy for private server connectivity
# ---------------------------------------------------------------------------
relay:
image: ghcr.io/tomszenessy/mino-relay:${RELAY_IMAGE_TAG:-main}
container_name: mino-relay
restart: unless-stopped
ports:
# Local-only by default when using tunnel. Set RELAY_PORT_BIND=0.0.0.0
# if you want to expose the port directly on the host instead.
- "${RELAY_PORT_BIND:-127.0.0.1}:${RELAY_PORT:-8787}:8787"
environment:
- NODE_ENV=production
- RELAY_PORT=8787
- RELAY_HOST=0.0.0.0
# Public URL that clients and servers use to reach this relay.
# Must match the domain routed to this container.
- RELAY_PUBLIC_BASE_URL=${RELAY_PUBLIC_BASE_URL:-https://relay.mino.ink}
healthcheck:
test:
[
"CMD",
"bun",
"-e",
"fetch('http://127.0.0.1:8787/api/v1/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
# ---------------------------------------------------------------------------
# Cloudflare Tunnel — Secure remote access (no open ports)
#
# CF_TUNNEL_TOKEN is required. Without it the container will exit.
# Remove this service entirely if you don't need tunnel access.
#
# Setup:
# 1. Go to https://one.dash.cloudflare.com
# 2. Networks/Tunnels → Create tunnel → Cloudflared
# 3. Copy token from the shown docker command: `... --token <TOKEN>`
# 4. Add public hostname: relay.mino.ink → HTTP → relay:8787
# 5. Set CF_TUNNEL_TOKEN in Portainer env vars
# ---------------------------------------------------------------------------
cloudflared:
image: cloudflare/cloudflared:latest
container_name: mino-relay-tunnel
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${CF_TUNNEL_TOKEN}
depends_on:
relay:
condition: service_started
Environment Variables
Set these in Portainer's "Environment variables" section when deploying the stack:
| Variable | Required | Default | Description |
|---|---|---|---|
CF_TUNNEL_TOKEN | Yes | (none) | Cloudflare Tunnel token for exposing the relay |
RELAY_PUBLIC_BASE_URL | Yes | https://relay.mino.ink | Public URL clients/servers use to reach relay |
RELAY_IMAGE_TAG | No | main | Docker image tag to pull |
RELAY_PORT | No | 8787 | Port the relay listens on |
RELAY_PORT_BIND | No | 127.0.0.1 | Host bind address (use 0.0.0.0 to expose directly) |
Test Domain Example
For test.mino.ink deployment, set:
CF_TUNNEL_TOKEN=<your-tunnel-token>
RELAY_PUBLIC_BASE_URL=https://relay.mino.ink
Verification
After deployment, confirm the relay is healthy:
curl https://relay.mino.ink/api/v1/health