Deploying HoloMUSH
Deploy a production HoloMUSH instance on a DigitalOcean droplet with automatic HTTPS, telnet support, and optional monitoring. The entire deployment runs in Docker and takes about 10 minutes.
Prerequisites
Section titled “Prerequisites”- A domain name you control (for HTTPS certificates)
- A DigitalOcean account (or any Linux host with Docker 24+)
Quick Start
Section titled “Quick Start”1. Create a Droplet
Section titled “1. Create a Droplet”In the DigitalOcean control panel:
- Click Create Droplet
- Choose Ubuntu 24.04 LTS
- Select the $6/mo plan (1 vCPU, 1 GB RAM) — sufficient for most games
- Choose a datacenter region close to your players
- Under Advanced Options, check Add Initialization Scripts
- Paste the cloud-init script into the User Data field
- Click Create Droplet
The script installs Docker, creates the deployment directory at
/opt/holomush/, and generates a configuration file with a random database
password.
2. Point Your DNS
Section titled “2. Point Your DNS”Create an A record pointing your domain to the droplet’s IP address:
mush.example.com → A → 203.0.113.42Wait for DNS to propagate (usually 1–5 minutes).
3. Configure
Section titled “3. Configure”SSH into your droplet and edit the configuration:
ssh root@your-droplet-ipnano /opt/holomush/.envSet DOMAIN to your actual domain:
DOMAIN=mush.example.comSave and exit.
4. Start the Stack
Section titled “4. Start the Stack”cd /opt/holomushdocker compose up -dOn first boot, HoloMUSH runs database migrations and seeds the default world (“The Crossroads”). This takes about 30 seconds.
5. Verify
Section titled “5. Verify”- Web client: Visit
https://your-domain— you should see the HoloMUSH landing page. Click “Try as Guest” to enter the game. - Telnet: Connect with any MU* client:
telnet your-domain 4201 - Logs:
docker compose logs -f coreto watch game events.
Block Storage (Recommended)
Section titled “Block Storage (Recommended)”For easier backups and the ability to resize storage independently:
- In DigitalOcean, create a Volume (10 GB is plenty to start)
- Attach it to your droplet
- Mount it:
# Mount the volume (DigitalOcean provides exact commands)mount -o defaults,nofail,discard /dev/disk/by-id/scsi-0DO_Volume_xxx /mnt/holomush
# Stop the stackcd /opt/holomush && docker compose down
# Move data to the volumemv /opt/holomush/data /mnt/holomush/data
# Update .envecho 'DATA_DIR=/mnt/holomush/data' >> /opt/holomush/.env
# Restartdocker compose up -dNow you can snapshot the volume for backups.
Monitoring (Optional)
Section titled “Monitoring (Optional)”HoloMUSH exports OpenTelemetry data (traces, metrics, logs) to any OTLP-compatible backend. Grafana Cloud offers a generous free tier (10K metrics series, 50GB logs, 50GB traces).
Set Up Grafana Cloud
Section titled “Set Up Grafana Cloud”- Sign up at grafana.com (free)
- Go to Connections → Add new connection → OpenTelemetry (OTLP)
- Copy the OTLP endpoint URL and generate an API token
- Add to
/opt/holomush/.env:
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlpOTEL_EXPORTER_OTLP_HEADERS=Basic <your-base64-token>- Start with the observability profile:
docker compose --profile observability up -dAny OTLP-compatible backend works — Axiom, SigNoz, Better Stack, and others
all accept the same format. Change the endpoint and headers in .env.
Upgrading
Section titled “Upgrading”Pull the latest images and restart:
cd /opt/holomushdocker compose pulldocker compose up -dDatabase migrations run automatically on startup. Check logs after upgrading:
docker compose logs core | head -50Backups
Section titled “Backups”The entire deployment lives in /opt/holomush/. Back it up with:
# Stop postgres to ensure consistencycd /opt/holomush && docker compose stop postgres
# Archivetar czf /tmp/holomush-backup-$(date +%Y%m%d).tar.gz /opt/holomush/
# Restartdocker compose start postgresOr snapshot the DigitalOcean volume if using block storage.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Check |
|---|---|
| HTTPS not working | DNS propagated? dig your-domain should show droplet IP |
| ”502 Bad Gateway” from Caddy | Gateway healthy? docker compose logs gateway |
| Telnet connection refused | UFW allows 4201? ufw status |
| Game world empty | Core bootstrap logs: docker compose logs core | grep bootstrap |
| Caddy cert errors | Port 80 open? ACME needs it for HTTP-01 challenge |
For detailed monitoring and health check information, see Operations.
Generic Linux Host
Section titled “Generic Linux Host”Any Linux host with Docker 24+ works. Skip the DigitalOcean-specific steps and instead:
- Install Docker: docs.docker.com/engine/install
- Download
compose.prod.yamlto/opt/holomush/compose.yaml - Download
docker/otel-collector/config.prod.yamlto/opt/holomush/config/otel-collector.yaml - Create
/opt/holomush/.env(see the generated.envin the cloud-init script for the template) - Create the data directories:
mkdir -p /opt/holomush/{data/postgres,config/certs,caddy/{data,config}} - Follow steps 3–5 from the Quick Start above
Alternative: Cloudflare Tunnel ingress
Section titled “Alternative: Cloudflare Tunnel ingress”If you want your server reachable only through Cloudflare (no public HTTP
ports open on the droplet), use the tunnel ingress profile instead of
Caddy. Cloudflare terminates TLS at its edge and your droplet makes an
outbound connection to Cloudflare — nothing connects inbound to 80 or 443.
-
In the Cloudflare dashboard, go to Zero Trust → Networks → Tunnels and create a new tunnel named
holomush. -
Copy the tunnel token (it starts with
eyJh...). -
In the tunnel’s Public Hostnames tab, add a route:
mush.example.com → http://gateway:8080. -
Set your cloud-init user-data to include these env vars:
Terminal window HOLOMUSH_INGRESS=tunnelHOLOMUSH_DOMAIN=mush.example.comCLOUDFLARE_TUNNEL_TOKEN=eyJh...your-token... -
Create the droplet with the cloud-init script. Once first boot completes,
https://mush.example.comis live.
Telnet (port 4201) still reaches your droplet directly — Cloudflare does
not proxy arbitrary TCP. Add a “DNS only” A record for
telnet.mush.example.com pointing at your droplet’s IP.
Automated nightly backups (encrypted)
Section titled “Automated nightly backups (encrypted)”To back up Postgres nightly to S3-compatible storage (DigitalOcean Spaces, AWS S3, Cloudflare R2) with client-side encryption, add these vars to your cloud-init:
BACKUP_S3_BUCKET=my-holomush-backupsBACKUP_S3_ENDPOINT=sfo3.digitaloceanspaces.com # omit for AWS S3BACKUP_S3_ACCESS_KEY=...BACKUP_S3_SECRET_KEY=...KOPIA_PASSWORD=... # long random string — encrypts every snapshotBACKUP_KEEP_DAILY=7 # optionalBACKUP_KEEP_WEEKLY=4 # optionalBACKUP_KEEP_MONTHLY=6 # optionalWhen set, the cloud-init enables the backups compose profile, which runs
a cron container that dumps Postgres at 03:00 UTC and streams it through
Kopia. Kopia encrypts the stream client-side (your
cloud provider cannot read the backups), deduplicates against previous
snapshots, compresses with zstd, and uploads. Retention is policy-based —
expired snapshots are pruned automatically.
Keep KOPIA_PASSWORD somewhere recoverable. If you lose it, every
snapshot in the repository becomes unrecoverable. Kopia has no recovery
backdoor.
Run a one-off backup via:
cd /opt/holomushdocker compose --profile caddy --profile backups exec backup /usr/local/bin/backup.sh(Replace --profile caddy with --profile tunnel if you use tunnel
ingress.)
Restore from a backup: see Restoring a backup.