Deployment
The deployment surface has three steps: bot infrastructure (identity), endpoint URL (where Teams sends activities), and sideloading (how users get the app). Sovereign-cloud tenants add one extra configuration.
For the detailed bot-infra walk-through, see references/bot-infra-setup.md. For sovereign clouds, see references/sovereign-clouds.md.
1. Bot infrastructure
The bot needs a registered identity Teams trusts. Two options:
- Teams-managed bot (recommended for new apps): the Teams Developer Portal allocates
BOT_ID+ secret and you do not host an AAD app for the bot identity. - Custom AAD app (single-tenant or multi-tenant): you own and rotate the secret yourself, useful when the same AAD app is shared with another workload.
Set up via the CLI:
teams app create my-bot # creates the app + bot registration
teams app get <teamsAppId> --json | jq . # see BOT_ID, password, etc.
Pipe the secret into .env:
BOT_ID=...
BOT_PASSWORD=...
TEAMS_APP_ID=...
The full setup walkthrough is at references/bot-infra-setup.md.
2. Endpoint URL
Teams delivers each activity to https://<host>/api/messages. The host changes between environments:
- Local dev —
https://<id>.devtunnels.ms/api/messagesorhttps://<id>.ngrok.io/api/messages. - Staging — your staging host (Azure App Service, AWS, Vercel, Render, etc.).
- Production — production host with a stable DNS name.
Update Teams after every host change:
teams app update <teamsAppId> --endpoint "https://<host>/api/messages"
<teamsAppId> is the Teams-side app id; do not confuse with the AAD client id.
3. Sideloading
After the endpoint is set:
- Open the Teams Developer Portal.
- Find the app — the CLI auto-registers on
teams project new. - Click Preview in Teams, accept the manifest, then send the bot a message.
For organisation-wide rollout, an admin uploads the manifest in the Teams Admin Center → Manage apps → Upload.
For an external app store listing, submit through Partner Center / AppSource. The manifest must pass automated validation: schema version, icon sizes, valid domains, and required webApplicationInfo for SSO.
4. Production hosting
The bot is a regular Node HTTP server. Any host that supports long-lived HTTPS works:
- Azure App Service — easiest fit; ships with App Service Authentication if you want managed AAD.
- AWS Fargate / Lambda + API Gateway — Lambda works only if you wrap activities in synchronous responses < 15s.
- Container hosts (Cloud Run, Fly, Render) — package the bot in a Docker image.
- Self-hosted Node behind nginx — standard reverse proxy + LE certs.
Required at runtime:
| Var | Source |
|---|---|
BOT_ID, BOT_PASSWORD |
teams app get |
TEAMS_APP_ID |
teams app list |
PORT |
host-provided |
OPENAI_API_KEY / AZURE_OPENAI_* |
if AI is enabled |
AAD_* |
if SSO/Graph is enabled |
Ensure logs are durable; the DevTools plugin is for local only — disable it in production:
const plugins = process.env.NODE_ENV === 'production' ? [] : [new DevtoolsPlugin()];
const app = new App({ plugins });
5. Sovereign clouds
import { App, US_GOV, US_GOV_DOD, CHINA, PUBLIC } from '@microsoft/teams.apps';
const cloud = process.env.CLOUD === 'USGov' ? US_GOV
: process.env.CLOUD === 'USGovDoD' ? US_GOV_DOD
: process.env.CLOUD === 'China' ? CHINA
: PUBLIC;
const app = new App({ cloud });
The cloud value swaps the login authority, bot service URL, and JWT issuer. See references/sovereign-clouds.md for the full table and per-cloud caveats.
6. CI/CD checklist
npm run build(ortsc) emits todist/.- Container image runs
node dist/index.jswithPORTfrom env. .envis supplied as runtime secrets, not committed.- Health probe: add
app.tab('health', '/health')or a separate Express route returning 200. - On deploy, re-run
teams app update --endpointonly if the URL changes.
Common pitfalls
- Sideload fails with "manifest invalid" — the manifest JSON schema version is older than the icons / features used; bump
manifestVersionto the latest. 401from Teams — bot password rotated but the running server still has the old one; restart with new.env.- Activities arrive in dev but not in prod — endpoint not updated for the production host.
- Sovereign cloud tokens rejected —
cloud: PUBLICagainst a USGov tenant; switch the enum value.