name: arcade services: arcade: build: . image: arcade:latest container_name: arcade restart: unless-stopped # No `ports:` key on purpose. Nothing is published to the host — the only # way in is through the tunnel. `expose` is documentation on a bridge # network, but it makes that intent obvious. expose: - '8080' networks: - edge # BusyBox wget ships in the nginx-alpine image, so no extra package. healthcheck: test: ['CMD-SHELL', 'wget -q -O - http://127.0.0.1:8080/healthz | grep -q ok'] interval: 30s timeout: 3s retries: 3 start_period: 5s # Static files only — nothing in this container ever writes to its own # filesystem. nginx-unprivileged needs these three paths writable. read_only: true tmpfs: - /tmp - /var/cache/nginx - /var/run security_opt: - no-new-privileges:true cloudflared: image: cloudflare/cloudflared:latest container_name: arcade-cloudflared restart: unless-stopped command: tunnel --no-autoupdate run environment: # Set TUNNEL_TOKEN in a sibling .env file (chmod 600, git-ignored). TUNNEL_TOKEN: ${TUNNEL_TOKEN:?set TUNNEL_TOKEN in .env} # Set to http2 if your router or ISP blocks outbound UDP/7844. TUNNEL_TRANSPORT_PROTOCOL: auto networks: - edge # Gates the tunnel on the origin actually serving. Without this, a restart # briefly presents 502s to anyone loading the app. depends_on: arcade: condition: service_healthy security_opt: - no-new-privileges:true networks: edge: driver: bridge