Back to skills
SKILL.md
Sentry Multi Env Setup
BSecurity'Configure Sentry across development, staging, and production environments
- 2,719 stars
- 0 votes
- 0 copies
- 0 views
- Added September 21, 2026
Works with
Security analysis
84/100- Installs packages at runtime which could introduce malicious dependencies
- Installs packages at runtime which could introduce malicious dependencies
Pro scans all 5 files and shows the line behind each finding
npx -y skills add jeremylongshore/claude-code-plugins-plus --skill sentry-multi-env-setup --agent claude-codeAre you the author of Sentry Multi Env Setup?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-sentry-multi-env-setup-dcf9a8c5)---
name: sentry-multi-env-setup
description: 'Configure Sentry across development, staging, and production environments
with separate DSNs, environment-specific sample rates, per-environment
alert rules, and dashboard filtering.
Use when setting up Sentry for dev/staging/production, managing
environment-specific configurations, isolating data between environments,
or configuring .env files for per-environment DSNs.
Trigger: "sentry environments", "sentry staging setup",
"multi-environment sentry", "sentry dev vs prod", "sentry DSN per env".
'
allowed-tools: Read, Write, Edit, Grep, Glob, Bash(npm:*), Bash(npx:*), Bash(node:*),
Bash(sentry-cli:*)
version: 1.51.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- sentry
- environments
- configuration
- multi-env
- dsn
- alerts
compatibility: Designed for Claude Code
---
# Sentry Multi-Environment Setup
## Overview
Configure Sentry to run across development, staging, and production with isolated DSNs, tuned sample rates, environment-aware alert routing, and dashboard filtering. Covers `@sentry/node` v8+ (TypeScript) and `sentry-sdk` v2+ (Python), targeting `sentry.io` or self-hosted Sentry 24.1+. The goal is to capture everything in dev, validate in staging, and protect production with tight sampling and PII scrubbing.
## Prerequisites
- Sentry organization at [sentry.io](https://sentry.io) with at least one project created
- `@sentry/node` v8+ installed (`npm install @sentry/node`) or `sentry-sdk` v2+ (`pip install sentry-sdk`)
- Environment naming convention agreed upon (this guide uses `development`, `staging`, `production`)
- DSN strategy decided: single project with environment tags or separate projects per environment (see [project-structure-options.md](references/project-structure-options.md))
- `.env` file management tooling (dotenv, direnv, or platform-native env config)
## Instructions
### Step 1 — Create Environment-Aware SDK Configuration with Separate DSNs
Each environment gets its own DSN pointing to a dedicated Sentry project. This prevents dev noise from inflating production quotas and allows independent rate limits per environment.
**Set up `.env` files per environment:**
```bash
# .env.development
SENTRY_DSN=https://dev-key@o0.ingest.sentry.io/111
SENTRY_ENVIRONMENT=development
SENTRY_RELEASE=local-dev
# .env.staging
SENTRY_DSN=https://staging-key@o0.ingest.sentry.io/222
SENTRY_ENVIRONMENT=staging
# .env.production
SENTRY_DSN=https://prod-key@o0.ingest.sentry.io/333
SENTRY_ENVIRONMENT=production
```
**TypeScript — environment-aware init with typed config:**
```typescript
// config/sentry.ts
import * as Sentry from '@sentry/node';
type Environment = 'development' | 'staging' | 'production';
interface EnvSentryConfig {
tracesSampleRate: number;
sampleRate: number;
debug: boolean;
sendDefaultPii: boolean;
maxBreadcrumbs: number;
enabled: boolean;
}
const ENV_CONFIG: Record<Environment, EnvSentryConfig> = {
development: {
tracesSampleRate: 1.0, // Capture every transaction for local debugging
sampleRate: 1.0, // Every error
debug: true, // Verbose console output for SDK troubleshooting
sendDefaultPii: true, // Include PII for local debugging only
maxBreadcrumbs: 100, // Full breadcrumb trail
enabled: true, // Set to false to fully disable in dev
},
staging: {
tracesSampleRate: 0.5, // 50% of transactions — enough to catch regressions
sampleRate: 1.0, // All errors — staging validates error handling
debug: false,
sendDefaultPii: false, // Staging mirrors prod PII policy
maxBreadcrumbs: 50,
enabled: true,
},
production: {
tracesSampleRate: 0.1, // 10% — balances visibility with quota budget
sampleRate: 1.0, // All errors — never drop production errors
debug: false,
sendDefaultPii: false, // Never send PII in production
maxBreadcrumbs: 50,
enabled: true,
},
};
export function initSentry(env?: Environment): void {
const environment = env
|| (process.env.SENTRY_ENVIRONMENT as Environment)
|| (process.env.NODE_ENV as Environment)
|| 'development';
const config = ENV_CONFIG[environment] || ENV_CONFIG.development;
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment,
release: process.env.SENTRY_RELEASE || `app@${process.env.npm_package_version || 'unknown'}`,
...config,
// Environment-specific event filtering
beforeSend(event) {
// Dev: capture everything for debugging
if (environment === 'development') return event;
// Staging: drop debug-level events to reduce noise
if (environment === 'staging' && event.level === 'debug') return null;
// Production: aggressive filtering
if (environment === 'production') {
// Drop known non-actionable browser errors
if (event.exception?.values?.some(e =>
e.value?.match(/ResizeObserver|Loading chunk|AbortError/)
)) return null;
// Scrub sensitive headers
if (event.request?.headers) {
delete event.request.headers['Authorization'];
delete event.request.headers['Cookie'];
delete event.request.headers['X-API-Key'];
}
}
return event;
},
});
}
```
**Python — equivalent multi-environment init:**
```python
# config/sentry_config.py
import os
import sentry_sdk
ENV_CONFIG = {
"development": {
"traces_sample_rate": 1.0,
"sample_rate": 1.0,
"debug": True,
"send_default_pii": True,
"max_breadcrumbs": 100,
},
"staging": {
"traces_sample_rate": 0.5,
"sample_rate": 1.0,
"debug": False,
"send_default_pii": False,
"max_breadcrumbs": 50,
},
"production": {
"traces_sample_rate": 0.1,
"sample_rate": 1.0,
"debug": False,
"send_default_pii": False,
"max_breadcrumbs": 50,
},
}
def init_sentry() -> None:
environment = os.environ.get(
"SENTRY_ENVIRONMENT",
os.environ.get("PYTHON_ENV", "development"),
)
config = ENV_CONFIG.get(environment, ENV_CONFIG["development"])
def before_send(event, hint):
if environment == "production":
exc_info = hint.get("exc_info")
if exc_info:
exc_type = exc_info[0]
# Drop expected exceptions in production
if exc_type in (KeyboardInterrupt, SystemExit):
return None
# Scrub PII from headers
request = event.get("request", {})
headers = request.get("headers", {})
for key in ("Authorization", "Cookie", "X-API-Key"):
headers.pop(key, None)
return event
sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN", ""),
environment=environment,
release=os.environ.get("SENTRY_RELEASE", "unknown"),
before_send=before_send,
**config,
)
```
### Step 2 — Configure Per-Environment Alert Rules and Dashboard Filtering
Alert routing prevents dev noise from waking on-call engineers. Each environment gets alert rules matching its severity tier.
**Production alert rules (Sentry dashboard > Alerts > Create Rule):**
| Alert Type | Condition | Filter | Action |
|-----------|-----------|--------|--------|
| Issue alert | New issue | `environment:production` | PagerDuty on-call |
| Metric alert | Error rate > 50/min for 5 min | `environment:production` | Slack `#alerts-critical` |
| Metric alert | P95 latency > 2s for 10 min | `environment:production` | Slack `#alerts-performance` |
**Staging alert rules:**
| Alert Type | Condition | Filter | Action |
|-----------|-----------|--------|--------|
| Issue alert | New issue (first seen) | `environment:staging` | Slack `#alerts-staging` |
| Metric alert | Error rate > 100/min for 5 min | `environment:staging` | Slack `#alerts-staging` |
**Development:** No alerts configured. Developers check the dashboard manually or use `debug: true` for console output.
**Add environment context tags for richer filtering:**
```typescript
// Add to Sentry.init() for dashboard filtering
Sentry.init({
// ...base config from Step 1
initialScope: {
tags: {
environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV,
region: process.env.AWS_REGION || process.env.GCP_REGION || 'us-east-1',
service: process.env.SERVICE_NAME || 'app',
deployment: process.env.DEPLOYMENT_ID || 'unknown',
},
},
});
// Dashboard filter queries:
// Issues > environment:production is:unresolved
// Performance > environment:staging service:api-gateway
// Discover > environment:production region:us-east-1
```
**Disable Sentry in test and CI environments:**
```typescript
// test/setup.ts — prevent test runs from sending events
const isTestEnv = process.env.NODE_ENV === 'test' || process.env.CI === 'true';
if (!isTestEnv) {
initSentry();
} else {
// Initialize with empty DSN — SDK loads but sends nothing
// All Sentry.captureException() calls become safe no-ops
Sentry.init({ dsn: '' });
}
```
### Step 3 — Wire CI/CD Releases to the Correct Environment
Tag Sentry releases with the deploy target so the Releases dashboard shows which commit is running where. Each `sentry-cli releases deploys` call records a deployment event on the timeline.
```bash
#!/usr/bin/env bash
# scripts/sentry-release.sh — call from CI after deploy
set -euo pipefail
VERSION="${SENTRY_RELEASE:-$(git rev-parse --short HEAD)}"
ENVIRONMENT="${1:?Usage: sentry-release.sh <environment>}"
ORG="${SENTRY_ORG:-my-org}"
PROJECT="${SENTRY_PROJECT:-my-app}"
echo "Creating Sentry release ${VERSION} for ${ENVIRONMENT}..."
# Create release and associate commits
sentry-cli releases --org "$ORG" --project "$PROJECT" new "$VERSION"
sentry-cli releases --org "$ORG" --project "$PROJECT" set-commits "$VERSION" --auto
# Upload source maps (if applicable)
if [ -d "./dist" ]; then
sentry-cli sourcemaps --org "$ORG" --project "$PROJECT" \
upload --release="$VERSION" ./dist
fi
# Finalize and record deployment
sentry-cli releases --org "$ORG" --project "$PROJECT" finalize "$VERSION"
sentry-cli releases --org "$ORG" --project "$PROJECT" \
deploys "$VERSION" new --env "$ENVIRONMENT"
echo "Release ${VERSION} deployed to ${ENVIRONMENT}"
# Usage in CI:
# ./scripts/sentry-release.sh staging # after staging deploy
# ./scripts/sentry-release.sh production # after production deploy
```
**GitHub Actions integration:**
```yaml
# .github/workflows/deploy.yml (snippet)
- name: Create Sentry release
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: my-org
SENTRY_PROJECT: my-app
run: |
npm install -g @sentry/cli
./scripts/sentry-release.sh ${{ github.event.inputs.environment || 'staging' }}
```
## Output
After completing all three steps you will have:
- **Per-environment `.env` files** with isolated DSNs pointing to separate Sentry projects
- **Typed SDK init module** (`config/sentry.ts` or `config/sentry_config.py`) that reads `SENTRY_ENVIRONMENT` and applies the correct sample rates, PII policy, and filtering
- **Alert rules** routed by environment: PagerDuty for production, Slack for staging, none for dev
- **Dashboard filter tags** (`environment`, `region`, `service`) for scoped queries
- **CI/CD release script** that tags deploys with the correct environment in Sentry's timeline
- **Test/CI guard** that disables Sentry during automated test runs
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Dev events appearing in production dashboard | Wrong DSN loaded at runtime | Verify `SENTRY_DSN` points to the correct project; use `dotenv` with `--env-file .env.production` |
| `environment: undefined` in Sentry events | `SENTRY_ENVIRONMENT` and `NODE_ENV` both unset | Set `SENTRY_ENVIRONMENT` explicitly in deployment config |
| Staging alerts routing to PagerDuty | Alert rule missing environment filter | Add `environment:production` condition to PagerDuty alert rules |
| Quota exhausted by dev/staging events | All environments share one Sentry project | Use separate projects per environment for independent quotas |
| Source maps not resolving in staging | Release version mismatch | Ensure `SENTRY_RELEASE` matches between `Sentry.init()` and `sentry-cli` upload |
| `beforeSend` dropping wanted events | Filter regex too broad | Test filters in staging first; log dropped events with `console.warn` during rollout |
| Python `sentry_sdk.init()` silently fails | DSN env var empty string | Check `os.environ.get("SENTRY_DSN")` is set; empty string disables the SDK |
## Examples
**TypeScript — Express app with multi-environment Sentry:**
```typescript
// app.ts
import express from 'express';
import * as Sentry from '@sentry/node';
import { initSentry } from './config/sentry';
// Initialize before any other imports that need instrumentation
initSentry();
const app = express();
// Sentry request handler — must be first middleware
Sentry.setupExpressErrorHandler(app);
app.get('/health', (_req, res) => res.json({ status: 'ok' }));
app.get('/api/data', async (_req, res) => {
const span = Sentry.startSpan({ name: 'fetch-data', op: 'db.query' }, () => {
// Your database call here
return { items: [] };
});
res.json(span);
});
// Error handler — Sentry captures unhandled errors automatically
app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
Sentry.captureException(err);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(3000, () => {
console.log(`Server running in ${process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV} mode`);
});
```
**Python — FastAPI with multi-environment Sentry:**
```python
# main.py
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import sentry_sdk
from config.sentry_config import init_sentry
# Initialize before app creation
init_sentry()
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/api/data")
async def get_data():
with sentry_sdk.start_span(op="db.query", name="fetch-data"):
# Your database call here
return {"items": []}
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
sentry_sdk.capture_exception(exc)
return JSONResponse(status_code=500, content={"error": "Internal server error"})
if __name__ == "__main__":
import uvicorn
env = os.environ.get("SENTRY_ENVIRONMENT", "development")
print(f"Server running in {env} mode")
uvicorn.run(app, host="0.0.0.0", port=8000)
```
## Resources
- [Sentry Environments](https://docs.sentry.io/product/sentry-basics/environments/) — official environment concepts
- [Filtering Events](https://docs.sentry.io/platforms/javascript/configuration/filtering/) — `beforeSend`, `ignoreErrors`, deny lists
- [Alert Configuration](https://docs.sentry.io/product/alerts/create-alerts/) — issue and metric alert rules
- [Releases & Deploys](https://docs.sentry.io/product/releases/) — commit tracking, deploy markers, suspect commits
- [sentry-cli Reference](https://docs.sentry.io/cli/) — release creation, source map upload, deploy recording
- [Python SDK Configuration](https://docs.sentry.io/platforms/python/configuration/options/) — `sentry_sdk.init()` options reference
- [Additional examples](references/examples.md) — end-to-end environment setup scenarios
## Next Steps
- **Per-route sampling**: Replace flat `tracesSampleRate` with `tracesSampler` for route-level control (see `sentry-performance-tuning` skill)
- **Feature flag integration**: Use Sentry's feature flag support to tie errors to specific flag variants
- **Environment promotion workflow**: Automate staging-to-production promotion with `sentry-cli releases deploys`
- **Cost monitoring**: Set per-project rate limits and budget alerts to catch runaway staging costs (see `sentry-cost-tuning` skill)
- **Distributed tracing**: Connect traces across services with `tracePropagationTargets` (see `sentry-reference-architecture` skill)
Files in this skill
- SKILL.md
- references/environment-configuration.md
- references/errors.md
- references/examples.md
- references/project-structure-options.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.