Deployment
ShockStack has two deploy shapes:
- Frontend on Cloudflare Workers — the Astro app runs at the edge with the
@astrojs/cloudflareadapter. - Full stack in Docker —
.NETAPI + Postgres + frontend, orchestrated by Compose (or any container platform).
Pick the one that matches your shape. They share CI.
Environment variables
| Variable | Where | Purpose |
|---|---|---|
DATABASE_URL | Frontend | Drizzle / Better Auth database connection (Postgres). |
BETTER_AUTH_SECRET | Frontend | HMAC secret for sessions. Rotate per environment. |
BETTER_AUTH_URL | Frontend | Public origin — e.g. https://app.example.com. Must match the live URL. |
CLOUDFLARE_API_TOKEN | CI | Token with Workers: edit scope. |
CLOUDFLARE_ACCOUNT_ID | CI | Target account. |
ConnectionStrings__DefaultConnection | Backend | EF Core connection string. |
Cors__Origins__0 | Backend | Allowed frontend origin. |
Never reuse dev secrets in prod
BETTER_AUTH_SECRET gates every session. Generate a fresh value per environment
(openssl rand -base64 32) and store it in your platform’s secret manager — not
in the repo.
Frontend: Cloudflare Workers
frontend/astro.config.* uses the Cloudflare adapter; frontend/wrangler.jsonc declares bindings (R2 bucket, compatibility flags).
Deploy the frontend
$ pnpm tokens:build
$ pnpm --filter frontend build
$ pnpm --filter frontend wrangler deploy
% pnpm tokens:build
% pnpm --filter frontend build
% pnpm --filter frontend wrangler deploy
PS C:\> pnpm tokens:build
PS C:\> pnpm --filter frontend build
PS C:\> pnpm --filter frontend wrangler deploy
CI authenticates with CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID. For secrets like BETTER_AUTH_SECRET, use:
pnpm --filter frontend wrangler secret put BETTER_AUTH_SECRET
R2 for file uploads
wrangler.jsonc ships with an R2 bucket binding. If you don’t need file storage, remove the binding; if you do, create the bucket once:
pnpm --filter frontend wrangler r2 bucket create shockstack-uploads
The server-side upload route reaches it via context.locals.runtime.env.UPLOADS (typed through env.d.ts).
Full stack: Docker
docker/docker-compose.yml brings up Postgres + the API + the frontend. Good for staging, self-hosted prod, or anywhere you don’t want edge.
Docker deploy
$ docker compose -f docker/docker-compose.yml build
$ docker compose -f docker/docker-compose.yml up -d
$ curl -f http://localhost:8080/health
% docker compose -f docker/docker-compose.yml build
% docker compose -f docker/docker-compose.yml up -d
% curl -f http://localhost:8080/health
PS C:\> docker compose -f docker\docker-compose.yml build
PS C:\> docker compose -f docker\docker-compose.yml up -d
PS C:\> curl -f http://localhost:8080/health
Push the built images to your registry (GHCR, ECR, etc.) and run them through your usual orchestrator — Kubernetes, Fly, Railway, a single VM. Nothing in the stack is Docker-specific beyond the Dockerfile.
CI/CD
.github/workflows/ uses paths-filter to run only what changed:
- frontend paths → lint → typecheck → test → build.
- backend paths →
dotnet restore→dotnet test→dotnet build. - tokens paths →
pnpm --filter @shockstack/tokens build.
semantic-release runs on main, reads Conventional Commits, bumps the version, generates CHANGELOG.md, and tags the release. Deploys are triggered from tags.
Rollout checklist
Use this before every cutover.
- Secrets set (
BETTER_AUTH_SECRET,DATABASE_URL, Cloudflare tokens). -
BETTER_AUTH_URLmatches the live origin. - Database migrations applied (
pnpm ss db migratelocally against the target, or via CI). - Health endpoints respond (
/healthon the backend,/api/healthon the frontend if enabled). - Sign-in & session roundtrip works from the production URL.
- Observability hooks in place (logs, traces, error reporting).
- Rollback plan: previous image tag on hand;
wrangler rollbackfor edge.
Rolling back
pnpm —filter frontend wrangler rollback [—version-id]docker compose up -d frontend —image ghcr.io/you/shockstack-frontend:PREVBoth are quick; preferred over trying to patch forward in an outage.