Self-hosting
Run your own game server with Docker and a Cloudflare tunnel.
The game server is one container with a data volume. A tunnel in front of it gives it a public HTTPS address without opening ports.
What you need
- Docker with Compose.
- The game assets (artwork, sprites, icons, cries and the battle font). They are generated from PokeAPI by the data script and mounted read-only.
- A build of the TapIn web app pointed at your server's address.
- A Cloudflare account if you want a stable public hostname.
Compose
name: pokemon
services:
server:
image: pokemon-server:local
build:
context: .
dockerfile: apps/pokemon-server/Dockerfile
environment:
PORT: "8790"
DATA_DIR: /data
ASSETS_DIR: /assets
WEB_DIR: /web
volumes:
- pokemon-data:/data
- ./assets:/assets:ro
- ./web-dist:/web:ro
ports:
- "127.0.0.1:8790:8790"
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8790/health"]
interval: 10s
tunnel:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run
environment:
TUNNEL_TOKEN: ${TUNNEL_TOKEN}
restart: unless-stopped
depends_on:
server:
condition: service_healthy
volumes:
pokemon-data:The server binds to localhost only; the tunnel is the way in. The SQLite database lives in the pokemon-data volume, so rebuilding the container keeps every trainer.
The tunnel
- In Cloudflare Zero Trust, create a named tunnel and copy its token into a
.envfile next to the compose file asTUNNEL_TOKEN. Keep that file out of version control. - Add a public hostname to the tunnel, for example
play.example.com, pointing athttp://server:8790. docker compose up -d. The hostname serves the web app, the API and the WebSocket on one origin.
For a quick test without an account, cloudflared tunnel --url http://server:8790 prints a temporary address; it changes on every restart.
Configuration
| Variable | Default | What it does |
|---|---|---|
PORT | 8790 | HTTP port inside the container. |
DATA_DIR | ./data | Folder for pokemon.sqlite. |
ASSETS_DIR | ./assets | Folder served at /pokemon-assets. |
WEB_DIR | ./web-dist | The built web app. |
GIGAVERSE_API_URL | Base URL of the TapIn API used to resolve users and communities. | |
FIREBASE_PROJECT_ID | The identity project whose ID tokens the server accepts. | |
TURN_SECONDS | 10 | Turn timer in battles between people. |
CHALLENGE_TTL_SECONDS | 30 | How long a challenge waits for an answer. |
ENCOUNTER_MIN_SECONDS / ENCOUNTER_MAX_SECONDS | 120 / 300 | The random gap between wild encounters. |
DISCONNECT_GRACE_SECONDS | 60 | How long a disconnected player's battle waits for them. |
POKEMON_DEBUG | off | 1 enables debug encounters (see below). Never on a public server. |
Debugging
With POKEMON_DEBUG=1, opening the app with ?pokemonDebug=1 shows a button that sends debug-encounter. speciesId picks a species (or null for a random one), -1 spawns a legendary flyover and -2 sends Team Rocket. Without the flag the server answers debug-disabled.
GET /health returns the server's status as JSON; use it for container health checks and uptime monitors.
TapIn Pokémon is an unofficial fan project. Pokémon and all related names and artwork are trademarks and © of Nintendo, Game Freak and Creatures. Legal