Add Docker + Caddy deploy for voxel.mxvs.art
Multi-stage Dockerfile compiles wasm client + axum server in one Rust builder and copies into a debian:bookworm-slim runtime (non-root uid). docker-compose.yml binds localhost:8080 by default; docker-compose.prod.yml replaces ports with a Caddy reverse proxy on host 80/443 that talks to the voxel container over the internal network. Caddy auto-issues Let's Encrypt certs. DEPLOY.md covers the three deployment modes (local-only, VPS with Cloudflare or Caddy, Cloudflare Tunnel from a workstation).
This commit is contained in:
parent
b52c1927cf
commit
f239a939ce
6 changed files with 238 additions and 0 deletions
16
.dockerignore
Normal file
16
.dockerignore
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Keep build artifacts out of the build context so the image stays small.
|
||||||
|
target/
|
||||||
|
**/target/
|
||||||
|
.git/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Generated wasm in web/ — we want the *build* to produce these.
|
||||||
|
web/voxel_game.js
|
||||||
|
web/voxel_game_bg.wasm
|
||||||
|
web/voxel_game.d.ts
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
12
Caddyfile
Normal file
12
Caddyfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Public reverse-proxy front. Mirrors the `.wg` Caddy convention but
|
||||||
|
# uses real Let's Encrypt certs (the wg version uses `tls internal`).
|
||||||
|
# Caddy fronts the voxel container over the Docker network, so the
|
||||||
|
# voxel service no longer publishes a host port.
|
||||||
|
{
|
||||||
|
email mgorog@gmail.com
|
||||||
|
}
|
||||||
|
|
||||||
|
voxel.mxvs.art {
|
||||||
|
encode zstd gzip
|
||||||
|
reverse_proxy voxel:8080
|
||||||
|
}
|
||||||
113
DEPLOY.md
Normal file
113
DEPLOY.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Deploying the voxel game
|
||||||
|
|
||||||
|
Three layers, pick the combination that fits.
|
||||||
|
|
||||||
|
## Local-only (development)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Serves on `http://localhost:8080`. `127.0.0.1`-bound — not reachable from
|
||||||
|
the network. Good for iterating on Docker without exposing anything.
|
||||||
|
|
||||||
|
## Anywhere with a public IP (the real deploy)
|
||||||
|
|
||||||
|
This assumes a cheap VPS with Docker + docker-compose-plugin installed.
|
||||||
|
Tested targets: Hetzner CPX11 (~$5/mo), DigitalOcean Basic Droplet ($4),
|
||||||
|
Vultr (~$2.50), Oracle Cloud Always Free tier (ARM Ampere instance — free
|
||||||
|
forever, just slow to provision).
|
||||||
|
|
||||||
|
### 1. SSH in and install Docker
|
||||||
|
|
||||||
|
Debian/Ubuntu host:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -fsSL https://get.docker.com | sh
|
||||||
|
sudo usermod -aG docker $USER # log out + back in
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Get the code on the box
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://maxgit.wg/max/terainia.git # or wherever you push it
|
||||||
|
cd terainia
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Build + run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
The container binds to `0.0.0.0:8080`. The first build takes ~5–8 min on
|
||||||
|
a small VPS because it compiles Rust + the wasm client; subsequent
|
||||||
|
builds are fast due to layer caching.
|
||||||
|
|
||||||
|
### 4. Put TLS in front
|
||||||
|
|
||||||
|
You have two clean options here.
|
||||||
|
|
||||||
|
**Option A — Cloudflare proxy (no cert on the VPS, simplest).**
|
||||||
|
|
||||||
|
- In your Cloudflare dashboard for `mxvs.art`, add an A record:
|
||||||
|
`voxel` → `<VPS public IP>`, proxy status **Proxied** (orange cloud).
|
||||||
|
- Cloudflare → SSL/TLS → set encryption mode to **Flexible**
|
||||||
|
(Cloudflare terminates HTTPS, talks HTTP to the VPS).
|
||||||
|
- That's it. `https://voxel.mxvs.art` serves the game.
|
||||||
|
|
||||||
|
**Option B — Caddy on the VPS for Let's Encrypt.**
|
||||||
|
|
||||||
|
If you want real end-to-end TLS instead of Cloudflare-terminated:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.prod.yml addition
|
||||||
|
caddy:
|
||||||
|
image: caddy:2-alpine
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy_data:/data
|
||||||
|
- caddy_config:/config
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
caddy_data:
|
||||||
|
caddy_config:
|
||||||
|
```
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
# Caddyfile
|
||||||
|
voxel.mxvs.art {
|
||||||
|
reverse_proxy voxel:8080
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then keep `voxel` bound to `127.0.0.1:8080` (or use Compose's internal
|
||||||
|
network only — drop the `ports:` mapping on the `voxel` service and let
|
||||||
|
Caddy reach it via the service name). The DNS A record in Cloudflare
|
||||||
|
should be **DNS only** (grey cloud) in this case so Cloudflare doesn't
|
||||||
|
re-terminate TLS.
|
||||||
|
|
||||||
|
### 5. Deploy updates
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git pull
|
||||||
|
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Down-and-up time is a couple of seconds while the new container
|
||||||
|
swaps in.
|
||||||
|
|
||||||
|
## Local + Cloudflare Tunnel (no VPS)
|
||||||
|
|
||||||
|
If you don't want a VPS yet, the game can run on your machine and be
|
||||||
|
exposed through a Cloudflare named tunnel pointing at `voxel.mxvs.art`.
|
||||||
|
The Dockerfile is still useful for keeping the local environment
|
||||||
|
consistent — just `docker compose up` and then point a `cloudflared`
|
||||||
|
container at `host.docker.internal:8080`. See the Cloudflare Tunnel
|
||||||
|
docs for the named-tunnel setup; the credential file (`cert.pem` from
|
||||||
|
`cloudflared tunnel login`) needs to land at `~/.cloudflared/cert.pem`
|
||||||
|
on whichever host the tunnel runs on.
|
||||||
52
Dockerfile
Normal file
52
Dockerfile
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Multi-stage build: compile the wasm client + the server in one Rust
|
||||||
|
# image, then copy the artifacts into a tiny Debian runtime.
|
||||||
|
FROM rust:1-bookworm AS builder
|
||||||
|
|
||||||
|
# Pin wasm-bindgen-cli to the same version we use during development. If
|
||||||
|
# this number drifts from Cargo.lock the wasm load will fail.
|
||||||
|
ARG WASM_BINDGEN_VERSION=0.2.122
|
||||||
|
|
||||||
|
# Install the wasm32 target and wasm-bindgen-cli.
|
||||||
|
RUN rustup target add wasm32-unknown-unknown && \
|
||||||
|
cargo install wasm-bindgen-cli --version ${WASM_BINDGEN_VERSION} --locked
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy the whole project. The .dockerignore keeps target/ out so we get
|
||||||
|
# a clean release build inside the container.
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the wasm client (release) and run wasm-bindgen to emit the
|
||||||
|
# JS glue + bg.wasm into web/.
|
||||||
|
RUN cargo build --target wasm32-unknown-unknown --release --lib && \
|
||||||
|
wasm-bindgen --target web --out-dir web --no-typescript \
|
||||||
|
target/wasm32-unknown-unknown/release/voxel_game.wasm
|
||||||
|
|
||||||
|
# Build the multiplayer server.
|
||||||
|
RUN cd server && cargo build --release
|
||||||
|
|
||||||
|
# ---- Runtime image ----
|
||||||
|
FROM debian:bookworm-slim AS runtime
|
||||||
|
|
||||||
|
# ca-certificates lets the server speak HTTPS if it ever needs to (it
|
||||||
|
# doesn't yet, but it's tiny and avoids surprises). tini handles signal
|
||||||
|
# forwarding so `docker stop` is clean.
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends ca-certificates tini && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /build/server/target/release/voxel-server /usr/local/bin/voxel-server
|
||||||
|
COPY --from=builder /build/web /app/web
|
||||||
|
|
||||||
|
ENV STATIC_DIR=/app/web
|
||||||
|
ENV PORT=8080
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run as non-root for safety.
|
||||||
|
RUN useradd --create-home --shell /bin/false --uid 10001 voxel && \
|
||||||
|
chown -R voxel:voxel /app
|
||||||
|
USER voxel
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
|
CMD ["/usr/local/bin/voxel-server"]
|
||||||
27
docker-compose.prod.yml
Normal file
27
docker-compose.prod.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Production overrides — Caddy on 80/443 terminates TLS and reverse
|
||||||
|
# proxies to the voxel container over the internal Docker network.
|
||||||
|
# The voxel service drops its host port mapping (was 127.0.0.1:8080:8080
|
||||||
|
# in the base file); Caddy reaches it via the service name. Run with:
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||||
|
services:
|
||||||
|
voxel:
|
||||||
|
ports: !override []
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
caddy:
|
||||||
|
image: caddy:2-alpine
|
||||||
|
container_name: voxel-caddy
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy_data:/data
|
||||||
|
- caddy_config:/config
|
||||||
|
depends_on:
|
||||||
|
- voxel
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
caddy_data:
|
||||||
|
caddy_config:
|
||||||
18
docker-compose.yml
Normal file
18
docker-compose.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
services:
|
||||||
|
voxel:
|
||||||
|
build: .
|
||||||
|
image: voxel-game:latest
|
||||||
|
container_name: voxel-game
|
||||||
|
restart: unless-stopped
|
||||||
|
# Bind to 127.0.0.1 by default so the container isn't accidentally
|
||||||
|
# public when running on a workstation. Override the LHS to 0.0.0.0
|
||||||
|
# in your deploy environment (or via docker-compose.override.yml) if
|
||||||
|
# you want the host's external interface to serve it.
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8080:8080"
|
||||||
|
environment:
|
||||||
|
# voxel-server reads these. STATIC_DIR is baked in by the Dockerfile,
|
||||||
|
# but you can override them here if mounting a different web/ from
|
||||||
|
# the host.
|
||||||
|
PORT: "8080"
|
||||||
|
RUST_LOG: "info"
|
||||||
Loading…
Add table
Reference in a new issue