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