factory-deployment
Skill9 starsChanged 4 months agoMITView on GitHub
Deployment and infrastructure conventions across builds. Vercel + Neon for web (with PR branch DBs), Cloud Run for Python services (one service per entry-point variant), GitHub Actions matrix-deploy on merge, Terraform with environments/modules layout for AWS / compliance customers, RDS IAM authentication for AWS DBs, Docker per Python entry point, env vars via t3-oss/env-nextjs, and the single-tenant customer-cloud deployment model (per the factory thesis). Migrations in CI, never at runtime.
- Reads credentials
- Installs packages
In this repository42 other files it gives its agents.
- guidance/claude.mdCLAUDE.md
- factory-animationSkill
- factory-api-route-engineerSkill
- factory-apiSkill
- factory-authSkill
- factory-auth-wiring-specialistSkill
- factory-ciSkill
- factory-closeSkill
- factory-code-reviewerSkill
- factory-commitsSkill
- factory-data-layerSkill
- factory-data-pipeline-engineerSkill
- factory-data-pipelinesSkill
- factory-db-migration-engineerSkill
- factory-db-migrationSkill
- factory-db-schema-architectSkill
- factory-designSkill
- factory-entrySkill
- factory-feature-architectSkill
- factory-forms-builderSkill
- factory-formsSkill
- factory-frontend-engineerSkill
- factory-frontendSkill
- factory-kit-auditSkill
- factory-llm-workflow-engineerSkill
- factory-llm-workflowsSkill
- factory-observabilitySkill
- factory-pitfallsSkill
- factory-promptingSkill
- factory-promptSkill
- factory-releaseSkill
- factory-security-engineerSkill
- factory-securitySkill
- factory-setup-linearSkill
- factory-stackSkill
- factory-standupSkill
- factory-state-modelingSkill
- factory-submitSkill
- factory-testingSkill
- factory-verification-engineerSkill
- factory-verificationSkill
- factory-voiceSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 3 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 83
Principle: Every PR gets a real ephemeral database via Neon branch.
- Distinctive: line 170
Don't ship past the build error.
- Distinctive: line 200
Principle: When AWS RDS is the database, use IAM authentication.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Factory deployment
Most of this skill is Recipe — deployment is where stack-specific decisions are the substance, not the surface. The few load-bearing principles (one service per entry point, migrations in CI, boot-time assertions, single-tenant customer-cloud as the commercial wedge) lead their sections; the rest are marked Recipe only.
Web — Vercel + Neon
Recipe only — stack pick.
| Aspect | Choice |
| Hosting | Vercel |
| Database | Neon (Postgres) — dev branches for PRs, prod for main |
| Migrations | drizzle-kit migrate in GitHub Actions, against ephemeral Neon branch |
| Env vars | Vercel project + t3-oss/env-nextjs Zod validation |
| Preview safety | Boot-time assertion: VERCEL_ENV=preview MUST NOT use prod DATABASE_URL |
Boot-time assertions for configuration safety
Principle. Configuration mistakes that could cause data loss or cross-environment writes fail loud at boot.
Why. A misconfigured preview deployment that connects to prod is the silent kind of incident — preview writes look like real writes, no error fires, the data contaminates for days. A boot-time assertion that compares VERCEL_ENV against DATABASE_URL makes the misconfiguration instant and visible. Three lines of code; one prevented incident pays for them many times over.
Recipe.
// src/db/index.ts — boot-time assertion
const isPreview = process.env.VERCEL_ENV === 'preview';
if (isPreview && process.env.DATABASE_URL === process.env.PROD_DATABASE_URL) {
throw new Error('Preview deployment cannot use prod DATABASE_URL');
}One Cloud Run service per Python entry point
Principle. Each Python entry-point variant (CLI, API, Pub/Sub) gets its own Cloud Run service with its own Dockerfile.
Why. Sharing a service across entry points means the startup command is conditional — if mode == 'api' run uvicorn; else run pubsub-handler — which makes the service definition impossible to validate at deploy time. Separate services have separate Dockerfiles, separate startup commands, separate scaling configurations. The cost is more Cloud Run services; the benefit is each one is independently deployable, scalable, and debuggable.
Recipe.
| Service | Entry point | Trigger |
myservice-api | main_api.py (FastAPI) | HTTP request |
myservice-pubsub | main_pubsub.py (handler) | Pub/Sub topic |
# Dockerfile.api
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen
COPY src/ ./src/
COPY main_api.py ./
ENV PYTHONUNBUFFERED=1
USER nonroot
CMD ["uv", "run", "uvicorn", "main_api:app", "--host", "0.0.0.0", "--port", "8080"]# Dockerfile.pubsub
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen
COPY src/ ./src/
COPY main_pubsub.py ./
ENV PYTHONUNBUFFERED=1
USER nonroot
CMD ["uv", "run", "python", "main_pubsub.py"]Non-root user; PYTHONUNBUFFERED=1 for log streaming.
GitHub Actions — ephemeral DBs + matrix deploy
Cross-reference.
factory-ci.mdowns the merge gate (typecheck, lint, test, build, claude-review). This section owns the deploy half — the Neon branch action for ephemeral PR DBs that thetestjob depends on, and the matrix-deploy jobs that run after the gate passes onmain.
Principle. Every PR gets a real ephemeral database via Neon branch; production data and dev data never mix in test pipelines.
Why. Shared dev DBs are a constant source of false test failures — a parallel PR mutated the schema, another test left stale rows, the test that "always passed" suddenly doesn't. Ephemeral per-PR DBs eliminate the shared state: each PR's tests run against a clean branch off main, deleted on PR close. The cost is one Neon project; the benefit is test reliability.
Recipe.
# .github/workflows/ci-cd.yml (sketch)
name: ci-cd
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: neondatabase/create-branch-action@v5
id: neon
with:
project_id: ${{ secrets.NEON_PROJECT_ID }}
parent: main
branch_name: pr-${{ github.event.pull_request.number }}
- run: pnpm install
- run: pnpm db:migrate
env:
DATABASE_URL: ${{ steps.neon.outputs.db_url }}
- run: pnpm test
deploy-web:
if: github.ref == 'refs/heads/main'
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: --prod
deploy-python:
if: github.ref == 'refs/heads/main'
needs: test
runs-on: ubuntu-latest
strategy:
matrix:
service: [myservice-api, myservice-pubsub]
steps:
- uses: actions/checkout@v4
- uses: google-github-actions/auth@v2
with: { credentials_json: '${{ secrets.GCP_SA_KEY }}' }
- run: gcloud run deploy ${{ matrix.service }} --source ./models/myservice ...Env vars — validate at build with Zod
Principle. Env vars are validated against a Zod schema at build time; the build fails if any required var is missing or wrong-shaped.
Why. Missing env vars surface at runtime as undefined reads — sometimes the page renders blank, sometimes a server action throws a cryptic error, sometimes auth silently degrades. Build-time validation makes the missing var fail at deploy, before any user sees the symptom. t3-oss/env-nextjs is the discriminated client/server split that keeps client bundles from leaking secrets.
Recipe.
// src/env.js
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
BETTER_AUTH_SECRET: z.string().min(32),
RESEND_API_KEY: z.string(),
RESEND_BAA_SIGNED: z.enum(['true', 'false']).optional(),
},
client: {
NEXT_PUBLIC_POSTHOG_KEY: z.string(),
NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(),
},
runtimeEnv: { ... },
});Don't ship past the build error.
Terraform — when AWS / compliance is in scope
Recipe only — pick when single-tenant customer-cloud deployment is the model. Use the environments/modules layout below; per-env values in terraform.tfvars, never hardcoded in main.tf.
infra/terraform/
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ │ └── ...
│ └── dev/
│ └── ...
└── modules/
├── database/ # RDS, parameter groups, backups
├── compute/ # ECS / Cloud Run / Fargate
├── networking/ # VPC, subnets, security groups
└── secrets/ # KMS, Secrets ManagerPick when:
- Customer requires single-tenant deployment in their cloud (HIPAA, FDA 21 CFR 820, SOC 2 Type II)
- AWS RDS (vs Neon) for compliance posture
- Need IaC for VPCs, security groups, KMS keys, IAM roles
RDS IAM authentication — no long-lived passwords
Principle. When AWS RDS is the database, use IAM authentication; never long-lived passwords in env vars.
Why. A long-lived DB password in an env var is a credential that can leak via logs, build artifacts, or a misconfigured secrets manager — and a leaked password is valid until rotated, which requires coordinated downtime. IAM tokens rotate every 14 minutes; a leaked token expires before exploitation is practical. The cost is a token-fetch helper and a refresh loop; the benefit is a credential-leak failure mode that bounds the blast radius.
Recipe.
// src/db/rds-auth.ts
import { Signer } from '@aws-sdk/rds-signer';
const signer = new Signer({
hostname: process.env.RDS_HOSTNAME!,
port: 5432,
username: process.env.RDS_USERNAME!,
region: process.env.AWS_REGION!,
});
async function getToken(): Promise<string> {
return signer.getAuthToken();
}Token rotates every 14 minutes; cache and refresh near expiry. Pair with a lazy DB singleton (Proxy-wrapped) so connections only open after IAM token is fetched.
Migrations — CI, never runtime
Principle. Migrations run in CI against an ephemeral branch DB or on a CI step before deploy; never at application startup.
Why. Same principle as factory-data-layer.md §migrations — CI, not runtime. Runtime migrations turn deployment into a database operation — slow boots, half-migrated states on failure, rollback-deploy-without-rollback-schema. CI migrations keep schema changes a separate gate.
Recipe.
# Local dev — schema push (fast iteration)
drizzle-kit push
# Pre-merge — generate migration file from schema diff
drizzle-kit generate
# CI — apply migrations against ephemeral PR DB
drizzle-kit migrate
# CI / merge to main — apply migrations against prod DB before deploying app
drizzle-kit migrateNever put drizzle-kit migrate in a Cloud Run CMD or Vercel build step.
Failure mode. Migrations in Cloud Run startup → 30-second cold starts; if a migration fails mid-startup, the service is in a broken half-migrated state with no clean recovery.
Single-tenant customer-cloud — the commercial wedge
Principle. For compliance-sensitive customers, deploy a single-tenant container into the customer's cloud; the factory is the substrate, not the host.
Why. Multi-tenant SaaS is incompatible with the compliance postures that high-value verticals require (HIPAA, FDA 21 CFR 820, SOC 2 Type II as a control, not as a marketing badge). Single-tenant customer-cloud lets the customer own data residency, secrets, and access — which makes their compliance officer's review fast — while the factory ships updates as pinned tags of the kit's repo. The trade-off vs SaaS: more per-customer onboarding, no shared infrastructure economies. The trade-off vs custom-built: dramatically less per-customer cost.
Recipe.
| Aspect | Choice |
| Deployment | Single-tenant container in customer's GCP or AWS account |
| Bootstrap | One-command Terraform module — "deploys in 2 hours" |
| Data residency | Customer's region — never crosses into the factory's infra |
| Secrets | Customer's AWS Secrets Manager / GCP Secret Manager — never in the factory repo |
| Updates | Customer pulls a new pinned tag of the factory-kit repo |
This is the differentiator vs. SaaS competitors (Retool, Superblocks). Customer data never leaves their cloud.
Source patterns
Encode/monorepo (Cloud Run with separate Dockerfile.api / Dockerfile.pubsub, GitHub Actions ephemeral Neon branches, matrix-deploy on merge, PostHog ingest rewrite), duezy (Terraform environments/modules layout, RDS IAM authentication with 14-min refresh, lazy DB singleton via Proxy), fleet-advisor (Vercel + Neon, t3-oss/env-nextjs Zod validation, monorepo deploy strategy), ford-analysis (VERCEL_ENV preview-DB safety check), Obsidian software-factory-idea (single-tenant customer-cloud deployment as commercial differentiator).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

