Skip to main content
The repository ships with a docker-compose.yml that starts the bot, dashboard, PostgreSQL, Redis, and the docs site together.

Prerequisites

  • Docker with the Compose plugin (Docker Desktop includes this)
  • A filled-in .env file in the repository root

Quick start

1

Copy and fill the environment file

cp .env.example .env
Set at minimum:
DISCORD_TOKEN=your_discord_bot_token
DISCORD_CLIENT_ID=your_discord_client_id
DISCORD_CLIENT_SECRET=your_discord_client_secret
ANTHROPIC_API_KEY=your_anthropic_api_key
SESSION_SECRET=your_session_secret
NEXTAUTH_SECRET=your_nextauth_secret
BOT_API_SECRET=your_bot_api_secret
NEXT_PUBLIC_DISCORD_CLIENT_ID=your_discord_client_id
DATABASE_URL and REDIS_URL are automatically overridden by the Compose file to point at the internal db and redis service hostnames. You do not need to set them for a Docker deployment.
2

Build and start all services

docker compose up -d
Docker builds the bot and dashboard images, then starts all services in dependency order: db and redis first, then bot and web once their health checks pass.
3

View logs

Stream logs from all services:
docker compose logs -f
Or tail a specific service:
docker compose logs -f bot
docker compose logs -f web

Service ports

ServicePortDescription
web6969Web dashboard
bot6968Bot REST API
db5432PostgreSQL
redis6379Redis
docs3100Documentation site

docker-compose.yml

docker-compose.yml
services:
  db:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
      POSTGRES_DB: volvoxbot
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d volvoxbot"]
      interval: 5s
      timeout: 5s
      retries: 5
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  bot:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    init: true
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "6968:6968"
    volumes:
      - botdata:/app/data
    env_file:
      - .env
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/volvoxbot
      DATABASE_SSL: "false"
      REDIS_URL: redis://redis:6379
      PORT: "6968"
      BOT_API_PORT: "6968"
      DASHBOARD_URL: http://localhost:6969
      DISABLE_PROMPT_CACHING: "1"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  web:
    build:
      context: ./web
      dockerfile: Dockerfile
      args:
        NEXT_PUBLIC_DISCORD_CLIENT_ID: ${NEXT_PUBLIC_DISCORD_CLIENT_ID}
    restart: unless-stopped
    init: true
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "6969:6969"
    env_file:
      - .env
    environment:
      PORT: "6969"
      DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/volvoxbot
      DATABASE_SSL: "false"
      REDIS_URL: redis://redis:6379
      NEXTAUTH_URL: http://localhost:6969
      BOT_API_URL: http://bot:6968
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  docs:
    build:
      context: ./docs
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "3100:3100"
    environment:
      PORT: "3100"
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  pgdata:
  redisdata:
  botdata:

Dockerfiles

The bot Dockerfile uses a multi-stage build. The deps stage installs only production dependencies, and the runner stage copies them into a minimal Alpine image running as a non-root user.
Dockerfile
# syntax=docker/dockerfile:1

# --- Dependencies ---
FROM node:22-alpine AS deps
RUN corepack enable
WORKDIR /app

COPY package.json pnpm-lock.yaml ./
RUN pnpm config set store-dir /tmp/pnpm-store && \\
    pnpm install --frozen-lockfile --prod --ignore-scripts && \\
    rm -rf /tmp/pnpm-store

# --- Production ---
FROM node:22-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 botgroup && \\
    adduser --system --uid 1001 botuser

# Copy production dependencies
COPY --from=deps --chown=botuser:botgroup /app/node_modules ./node_modules

# Copy application source, config, and migrations
COPY --chown=botuser:botgroup package.json ./
COPY --chown=botuser:botgroup config.json ./
COPY --chown=botuser:botgroup src/ ./src/
COPY --chown=botuser:botgroup migrations/ ./migrations/

# Create data directory for state persistence
RUN mkdir -p data && chown botuser:botgroup data

USER botuser

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \\
  CMD wget --spider --quiet "http://localhost:${PORT:-3001}/api/v1/health" || exit 1

CMD ["node", "src/index.js"]

Volume mounts and data persistence

Three named volumes preserve data across container restarts:
VolumeMountContents
pgdata/var/lib/postgresql/dataPostgreSQL data directory
redisdata/dataRedis RDB snapshots
botdata/app/dataBot state persistence (local data directory)
Removing volumes with docker compose down -v permanently deletes your database and Redis data. Omit the -v flag to keep volumes when stopping services.

Common commands

# Start all services in the background
docker compose up -d

# Stop all services (keeps volumes)
docker compose down

# Rebuild images after code changes
docker compose up -d --build

# Stream logs from all services
docker compose logs -f

# Restart a single service
docker compose restart bot

# Open a shell in the bot container
docker compose exec bot sh

# Run a database migration manually
docker compose exec bot pnpm migrate

Railway deployment

Volvox.Bot includes a railway.toml for one-click deployment to Railway.
railway.toml
[build]
builder = "DOCKERFILE"
dockerfilePath = "Dockerfile"
dockerLayerCaching = true

[deploy]
healthcheckPath = "/api/v1/health"
healthcheckTimeout = 10
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10
numReplicas = 1
To deploy on Railway:
  1. Install the Railway CLI and log in.
  2. Create a new Railway project and link it to your repository.
  3. Add a PostgreSQL and Redis service from the Railway plugin library.
  4. Set your environment variables in the Railway dashboard under Variables.
  5. Deploy with:
railway up
Railway automatically detects railway.toml, builds from the Dockerfile, and health-checks the bot at /api/v1/health before marking the deployment as live.
Railway internal networking uses .railway.internal hostnames. The bot’s SSL configuration automatically disables TLS for internal Railway connections, so you do not need to set DATABASE_SSL=false manually.

Build docs developers (and LLMs) love