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¶
- A domain name you control (for HTTPS certificates)
- A DigitalOcean account (or any Linux host with Docker 24+)
Quick Start¶
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¶
Create an A record pointing your domain to the droplet's IP address:
Wait for DNS to propagate (usually 1–5 minutes).
3. Configure¶
SSH into your droplet and edit the configuration:
Set DOMAIN to your actual domain:
Save and exit.
4. Start the Stack¶
On first boot, HoloMUSH runs database migrations and seeds the default world ("The Crossroads"). This takes about 30 seconds.
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)¶
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 stack
cd /opt/holomush && docker compose down
# Move data to the volume
mv /opt/holomush/data /mnt/holomush/data
# Update .env
echo 'DATA_DIR=/mnt/holomush/data' >> /opt/holomush/.env
# Restart
docker compose up -d
Now you can snapshot the volume for backups.
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¶
- 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/otlp
OTEL_EXPORTER_OTLP_HEADERS=Basic <your-base64-token>
- Start with the observability profile:
Any OTLP-compatible backend works — Axiom, SigNoz, Better Stack, and others
all accept the same format. Change the endpoint and headers in .env.
Upgrading¶
Pull the latest images and restart:
Database migrations run automatically on startup. Check logs after upgrading:
Backups¶
The entire deployment lives in /opt/holomush/. Back it up with:
# Stop postgres to ensure consistency
cd /opt/holomush && docker compose stop postgres
# Archive
tar czf /tmp/holomush-backup-$(date +%Y%m%d).tar.gz /opt/holomush/
# Restart
docker compose start postgres
Or snapshot the DigitalOcean volume if using block storage.
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¶
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¶
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:
-
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)¶
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-backups
BACKUP_S3_ENDPOINT=sfo3.digitaloceanspaces.com # omit for AWS S3
BACKUP_S3_ACCESS_KEY=...
BACKUP_S3_SECRET_KEY=...
KOPIA_PASSWORD=... # long random string — encrypts every snapshot
BACKUP_KEEP_DAILY=7 # optional
BACKUP_KEEP_WEEKLY=4 # optional
BACKUP_KEEP_MONTHLY=6 # optional
When 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/holomush
docker 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.