Dockerfile. The final image is a minimal Alpine-based Node.js container that runs the compiled bot as a single CJS bundle.
The Dockerfile
The build uses three stages: a sharedbase layer, a build stage that compiles the bot, and a lean final image that only contains the production output.
Dockerfile
Stage breakdown
base — node:24-alpine with two system packages added:
ripgrep— required by theEffectReposervice for fast code search across the cloned Effect source tree.git— required to clone the Effect repository at runtime.
build — copies the full monorepo, installs all dependencies with a pnpm cache mount for faster rebuilds, then:
- Runs
tsupvia thediscord-botbuild script to producedist/main.cjs. - Runs
pnpm deploy --prodto produce a self-contained deployment directory at/prod/discord-botcontaining only production dependencies.
/prod/discord-bot from the build stage. This keeps the final image small by discarding dev dependencies, TypeScript sources, and the rest of the monorepo.
Port
8080 is exposed for health check probes (used by Fly.io). The bot itself is a pure Discord Gateway client and does not serve HTTP traffic.Building the image
Run this from the repository root:The repository also contains a
runner.Dockerfile for a separate runner package (not the Discord bot). This file is unrelated to deploying the bot and can be ignored.Running the container
Pass required environment variables with-e flags or a .env file:
Local observability with Docker Compose
The repository includes adocker-compose.yaml that spins up a local Grafana OTEL stack for development tracing:
docker-compose.yaml
http://localhost:4318 when HONEYCOMB_API_KEY is not set. Open http://localhost:4000 to view traces in Grafana.
Deploying to Fly.io
The repository includes afly.toml that targets the iad (us-east) region on a shared-cpu-1x VM with 512 MB of memory:
fly.toml