Run SoulFire with Docker Compose
Start a persistent local container, generate access, and add HTTPS for remote clients.
Install Docker Engine and Compose using the official installation guide.
The official image contains the Java runtime and exposes the API on container port 38765.
Persistent data belongs at /soulfire/data.
Start a local deployment
Create a deployment directory with this compose.yaml:
services:
app:
image: ghcr.io/soulfiremc-com/soulfire:latest
restart: unless-stopped
stdin_open: true
tty: true
ports:
- "127.0.0.1:38765:38765"
volumes:
- ./data:/soulfire/dataThis publishes HTTP only on the host's loopback address.
For a repeatable deployment, replace latest with the released tag or digest you choose.
Record the image version with your test results.
Create the writable data directory on a Linux host:
mkdir -p data
sudo chown 1001:1001 data
docker compose up -dThe image runs as UID/GID 1001.
Check the container and recent output:
docker compose ps
docker compose logs --tail=100 app
curl -f http://127.0.0.1:38765/healthWait for startup to complete. A successful health response checks backend availability, not Minecraft connectivity.
Generate access
Attach to the backend console:
docker compose attach appRun generate-token api in that console.
Detach with Ctrl+P, Ctrl+Q to keep the container running.
Connect a desktop client to http://127.0.0.1:38765 with that token from the same host.
For a bot target, remember that 127.0.0.1 inside the container refers to the container.
Use a Minecraft address reachable from the container network.
Then complete the first-bot tutorial.
Add a Cloudflare tunnel
Create a tunnel and route using Cloudflare's remote tunnel guide.
Set its service target to http://app:38765.
Save the tunnel token in your deployment's .env:
TUNNEL_TOKEN=YOUR_TUNNEL_TOKENAdd a service to the same Compose project:
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel run
environment:
TUNNEL_TOKEN: ${TUNNEL_TOKEN}Run docker compose up -d after saving the change.
Connect the client through the tunnel's HTTPS hostname.
Keep the local API port bound to loopback, or remove the published port if only the tunnel needs access.
Use Traefik with a hostname
Point your hostname to this host and make port 443 reachable for the TLS challenge.
Set these values in .env:
DOMAIN=soulfire.example.com
EMAIL=admin@example.comUse this Compose deployment instead of the local-only example:
services:
app:
image: ghcr.io/soulfiremc-com/soulfire:latest
restart: unless-stopped
stdin_open: true
tty: true
volumes:
- ./data:/soulfire/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.soulfire.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.soulfire.entrypoints=websecure"
- "traefik.http.routers.soulfire.tls.certresolver=soulfire"
- "traefik.http.services.soulfire.loadbalancer.server.port=38765"
traefik:
image: traefik:v3
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.soulfire.acme.tlschallenge=true"
- "--certificatesresolvers.soulfire.acme.email=${EMAIL}"
- "--certificatesresolvers.soulfire.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
volumes:
letsencrypt:Check the Traefik log for certificate errors before connecting the client.
Preserve its letsencrypt volume across replacements.
Use Traefik's ACME reference for challenge and renewal details.
For IP-only HTTPS, use the automated setup or current upstream IP certificate instructions.
Panels and maintenance
For Pterodactyl or Pelican, use the official SoulFire egg. Preserve the panel's data mount and use the same access and HTTPS checks.
Read persistent data, backup and restore, and upgrade and rollback before replacing containers. Do not remove a data volume as a routine upgrade step.
How is this page?
Last updated on
