Guide 21

CI/CD for bots: automated tests, secrets, safe deploys, webhook switching and rollbacks

A bot is a long-running service with a public identity. Deploys should be boring, repeatable and reversible.

Guide2 min readUpdated

Pipeline stages

  1. Check: lint, type-check and run unit and fixture tests (testing bots).
  2. Build: produce one artifact (container image, bundle) tagged with the commit SHA.
  3. Deploy to staging: run against the staging bot and smoke-test it.
  4. Promote to production: the same artifact, different configuration.
  5. Verify: automated smoke checks and a short watch of error rates.

Secrets

  • Store tokens in the CI provider's secret store or a secrets manager, scoped to the environment that needs them. Production secrets should be available only to the production deploy job.
  • Never print environment variables in logs, and enable secret masking.
  • Rotate tokens when people leave or a log leaks. Telegram tokens can be revoked in BotFather; Discord and Slack tokens can be regenerated in their developer consoles.
  • Validate configuration before starting with a fail-fast loader; the .env validator generates one.

Staging and production bots

Use different bot accounts, tokens and test chats for each environment. This keeps experiments out of real users' chats and lets you test command registration and webhooks safely.

Platform state as code

Some bot configuration lives on the platform, so manage it in the pipeline:

  • Discord: register application commands with a bulk overwrite (PUT /applications/{id}/commands) from a versioned JSON file. Use guild commands for staging; they update instantly. Build them with the Discord command builder.
  • Telegram: call setMyCommands from the same list you use in code (command list builder) and setWebhook with the secret token after deploy.
  • Slack: keep the app manifest in the repo and apply changes deliberately.

Releasing without downtime

  • Webhook bots behind a load balancer: roll out new instances, check health, then shift traffic. Make handlers idempotent because a retry may hit old and new versions.
  • Polling bots must run a single consumer per token. Stop the old process before the new one starts polling, or Telegram returns 409 Conflict. A short gap is acceptable; updates wait on the server for up to 24 hours.
  • Gateway bots (Discord) reconnect and resume; for large bots roll shards gradually.

Migrations and rollback

  • Make database changes backward compatible: add columns first, deploy code, remove old columns later.
  • Keep the previous artifact and a one-command rollback. Rolling back code is easy; rolling back platform state (commands, webhooks) needs the previous config too, so version it.

Example (GitHub Actions)

name: deploy
on: { push: { branches: [main] } }
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci && npm test
  deploy:
    needs: test
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy.sh
        env:
          BOT_TOKEN: ${{ secrets.BOT_TOKEN }}

Generate container and service files with the hosting config generator.

Check your understanding

1. Why does a polling bot need care during deploys?
2. Where should production tokens live?