Back to Blog
Tutorial9 min read2026-07-04

How to Deploy a Slack Bot to the Cloud

Deploy a Slack bot to production: choosing between Socket Mode and HTTP events, handling Slack's 3-second response deadline, securing signing secrets, and going from local development to a live, always-on bot.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

A Slack bot has one deployment characteristic that shapes everything: Slack expects an acknowledgment within three seconds, or it retries the event and shows the user an error. That deadline drives how you architect handlers and why an always-on, low-latency deployment matters. This guide covers deploying a Slack bot built with Bolt (Slack's official framework) and the two connection modes you can choose.

Socket Mode vs HTTP events

Slack offers two ways for your bot to receive events:

  • HTTP (Events API): Slack POSTs events to a public HTTPS endpoint you expose. Requires a publicly reachable URL with a valid certificate.
  • Socket Mode: your bot opens an outbound WebSocket to Slack; no public endpoint needed.

Socket Mode is great for development and for bots behind firewalls, since there's nothing to expose. HTTP events scale better and are the more common production choice because the deployment is a standard stateless web service. We'll cover both, but lean toward HTTP for production.

The Bolt app

A minimal Bolt app handling a slash command and an event:

import pkg from '@slack/bolt';
const { App } = pkg;

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  // For Socket Mode, add: socketMode: true, appToken: process.env.SLACK_APP_TOKEN
});

app.command('/status', async ({ ack, respond }) => {
  await ack(); // acknowledge within 3 seconds
  await respond('All systems operational.');
});

app.event('app_mention', async ({ event, say }) => {
  await say(`Hi <@${event.user}>!`);
});

await app.start(process.env.PORT || 3000);

The ack() call is the three-second deadline in action: acknowledge immediately, then do slow work afterward. If a handler needs to call an external API that might be slow, ack first and post the result via respond or chat.postMessage once it's ready.

Security: verify the signing secret

For HTTP mode, Slack signs every request. Bolt verifies the signature automatically using your SLACK_SIGNING_SECRET, which prevents anyone from forging events to your endpoint. Never disable this check and never log the secret. Keep all three values, bot token, signing secret, and (for Socket Mode) app token, in environment variables, not in code.

Dockerfile

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]

The bot listens on a port (HTTP mode) or maintains an outbound socket (Socket Mode). Either way it's a long-running container.

Deploying on PandaStack

  1. 1Connect your GitHub repo as a container app.
  2. 2Add SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, and for Socket Mode SLACK_APP_TOKEN as environment variables in the dashboard.
  3. 3PandaStack builds with rootless BuildKit, pushes to Artifact Registry, and deploys via Helm. You get an HTTPS URL with automatic SSL.
  4. 4For HTTP mode, set your bot's Request URL in the Slack app config to https://your-app.pandastack.app/slack/events. Slack sends a one-time URL verification challenge that Bolt answers automatically.
  5. 5Tail the live logs to confirm the bot started and the URL verified.
ModeNeeds public URLBest for
HTTP eventsyes (auto SSL covers it)production, scalable
Socket Modenodev, firewalled environments, simpler setup

A note on scale-to-zero

This is the key deployment decision for a Slack bot. The three-second deadline means cold starts are risky: if your bot scaled to zero and Slack sends an event, the cold start might blow the deadline and Slack retries (or shows an error). For a production bot, run on a tier that keeps a warm instance. For a low-stakes internal bot where the occasional retry is fine, the free tier's scale-to-zero on spot nodes works, just be aware of the tradeoff. Socket Mode bots especially should stay warm, since a scaled-down bot drops its WebSocket entirely and goes offline.

If your bot stores state (user preferences, reminders), attach a managed PostgreSQL or Redis database; DATABASE_URL is injected automatically and you can persist across restarts and redeploys.

Handling retries idempotently

Because Slack retries on missed acks, your handlers should be idempotent where it matters. Slack includes a retry header (X-Slack-Retry-Num); if you're performing a non-idempotent action (sending an external message, charging something), dedupe on the event ID so a retry doesn't double-fire.

Verifying

Invite the bot to a channel and try your slash command or mention it. Watch the live logs for the incoming event and the ack. Use the server-side metrics to confirm latency stays well under the three-second budget.

Common pitfalls

  • Slow handlers without early ack trip the three-second deadline.
  • Cold starts from scale-to-zero cause missed deadlines, keep production bots warm.
  • Leaking the signing secret in logs or code.
  • Non-idempotent handlers double-fire on Slack retries.

References

  • Slack Bolt for JavaScript: https://tools.slack.dev/bolt-js/
  • Slack Events API: https://api.slack.com/apis/events-api
  • Slack Socket Mode: https://api.slack.com/apis/socket-mode
  • Verifying requests from Slack: https://api.slack.com/authentication/verifying-requests-from-slack
  • Slack app tokens and scopes: https://api.slack.com/concepts/token-types

A Slack bot is a small always-on service where latency and warmth matter more than scale. Deploy yours with environment-managed secrets and an optional managed database on PandaStack's free tier: https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also