- Use floating node:alpine that tracks the latest supported LTS; pnpm bootstrap follows package.json's packageManager pin. - Drop corepack (removed from node:26), install pnpm via npm global. - Add pnpm fetch + offline install for stable dependency-layer caching. - Run as non-root nextjs (UID/GID 33 = host www-data) with tini as PID 1 for correct signal handling. - Open node engines to >=20.9.0 so patches/minors float automatically. - Add docker-preflight.sh (per-VPS checks incl. --fix) and gate docker-update.sh so Node major upgrades require explicit review while patches deploy silently.
81 lines
3.3 KiB
YAML
81 lines
3.3 KiB
YAML
services:
|
|
cms:
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile
|
|
# The host disables Docker iptables (daemon.json: "iptables": false), so
|
|
# build containers on the bridge network have no outbound NAT/DNS. Build on
|
|
# the host network instead so pnpm/npm/yarn can reach the registry.
|
|
network: host
|
|
container_name: epicnext-cms
|
|
# Runs on the host network so existing 127.0.0.1 refs in .env keep working:
|
|
# MariaDB (3306), DragonflyDB/Redis (6379), emulator RCON (3003) + API (3001),
|
|
# and the imaging renderer (8082). The container then listens directly on the
|
|
# host's 3002 (the same port the current host-side CMS uses).
|
|
# NOTE: stop the host CMS (next-server on 3002) first, otherwise the port is taken.
|
|
network_mode: host
|
|
restart: unless-stopped
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
- HOSTNAME=0.0.0.0
|
|
volumes:
|
|
# ── Write targets (runtime imports/uploads, persistent on the host) ──
|
|
# The CMS writes imported furni/figures/pets/effects here (see
|
|
# src/lib/services/furni-asset-dirs.ts). These must be RW, and owned by
|
|
# UID/GID 33 (www-data) on the host so the container user can write to them:
|
|
# sudo chown -R 33:33 ./public/nitro-assets ./public/swf ./storage
|
|
- ./public/nitro-assets:/app/public/nitro-assets
|
|
- ./public/swf:/app/public/swf
|
|
# Runtime uploaded media (persistent on the host).
|
|
- ./storage:/app/storage
|
|
|
|
# ── Shared gamedata (absolute path the CMS hardcodes & writes to) ──
|
|
# src/lib/services/furni-asset-dirs.ts: `DEFAULT_GAMEDATA_ROOT =
|
|
# /var/www/Gamedata`. nginx on the host also serves /gamedata/ from this
|
|
# same directory, so mount it into the container at the same absolute path.
|
|
# Must be RW so furniture/badge imports can write mirrors to it.
|
|
- /var/www/Gamedata:/var/www/Gamedata
|
|
|
|
healthcheck:
|
|
test: ["CMD", "node", "-e", "fetch('http://localhost:3002/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 40s
|
|
mem_limit: 2g
|
|
|
|
# ── FlareSolverr (Cloudflare bypass for clone sources) ──
|
|
# Solves Cloudflare/TLS-fingerprint blocks via a headless Chrome browser.
|
|
# The CMS calls this on http://localhost:8191 for sources that block Node's
|
|
# TLS fingerprint (e.g. Leet.city). Used by src/lib/services/flare-solver.ts.
|
|
flaresolverr:
|
|
image: ghcr.io/flaresolverr/flaresolverr:latest
|
|
container_name: flaresolverr
|
|
network_mode: host
|
|
restart: unless-stopped
|
|
environment:
|
|
- LOG_LEVEL=info
|
|
- BROWSER_TIMEOUT=60000
|
|
- BROWSER_WORKERS=1
|
|
mem_limit: 2g
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-sf", "http://localhost:8191/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 30s
|
|
|
|
# ── Opt-in: Octane-Renderer (Habbo avatar imager) ──
|
|
# Serves /imaging on port 3030 (the CMS proxies /imaging to it). Renders
|
|
# avatars into /var/www/Gamedata/habbo-imaging, so it needs RW access.
|
|
# Disabled by default — uncomment to run the renderer as a container.
|
|
# imager:
|
|
# build:
|
|
# context: /var/www/Octane-Renderer
|
|
# container_name: epicnext-octane-renderer
|
|
# ports:
|
|
# - "3030:3030"
|
|
# restart: unless-stopped
|
|
# volumes:
|
|
# - /var/www/Gamedata:/var/www/Gamedata |