Home/Blog

Startup Infrastructure Checklist: Zero to Production

A phased startup infrastructure checklist: accounts and access, CI/CD, hosting, backups, observability, security and on-call, with configs to copy.

Startup Infrastructure Checklist: Zero to Production

A startup needs a small, specific set of infrastructure before real users arrive: company-owned accounts with MFA, CI on every pull request, automatic deploys with a rollback you have practiced, a managed database with point-in-time recovery and a restore you have actually performed, error tracking and uptime alerts, secrets kept out of the repository, and one named person on call. Kubernetes, multi-region and service meshes can wait until a measured problem needs them. The order matters more than the tools: access first, then delivery, then data safety, then visibility, then security hardening and operations.

Key takeaways

  • Do accounts and access first. The most expensive early mistake is a cloud root account, domain or code host owned by one founder's personal email.
  • A backup you have never restored is a hope, not a backup. Restore one before launch and repeat it every quarter.
  • CI runs on every pull request and stays fast. Merging to main deploys automatically, and rollback is a button you have pressed.
  • Alert on what users feel (errors, latency, the site being down), not on every CPU spike.
  • Keep secrets out of git from the first commit with a pre-commit scanner, and enforce MFA on every account that can reach production.
  • Defer Kubernetes, multi-region and microservices until you can name the problem they solve.

The phases at a glance

PhaseWhenOutcome
0. Accounts and accessDay 1The company owns every account; MFA everywhere
1. Repository and CIDays 1 to 3Every PR is linted, type-checked, tested and built
2. Hosting and deploysDays 3 to 7Merge deploys automatically; rollback rehearsed; preview environments
3. Database and data safetyWeeks 1 to 2Managed PostgreSQL, migrations in CI, point-in-time recovery, restore tested
4. ObservabilityWeek 2Errors, logs, uptime and latency visible, with alerts that mean something
5. Security baselineWeeks 2 to 3Secrets managed, dependencies scanned, rate limits, email authentication
6. Operational readinessWeeks 3 to 4On-call, runbooks, an incident process and a recovery document

Phase 0: accounts and access

Nobody puts this phase in a sprint, and it is the one that causes the worst weeks later: the domain that lapses because the renewal card expired, the cloud root account tied to a co-founder who left, the GitHub organization with one owner who lost their phone.

  • Domain registered to the company, registrar account with MFA, registrar lock on, auto-renew on, and a renewal card that someone checks
  • DNS at a provider with MFA and more than one admin
  • Cloud accounts created with a company alias address (for example cloud-root@yourcompany.com), root or owner credentials in the company password manager, a hardware security key on the root account, and day-to-day work done through individual accounts
  • Code host organization owned by the company, with at least two owners and two-factor authentication required for every member
  • Password manager for the company, with shared vaults for infrastructure credentials
  • Single sign-on through your Google Workspace or Microsoft 365 identity for every tool that supports it
  • Budget alerts on every cloud and SaaS account that bills by usage
  • Access inventory: one document listing every system, who has admin rights, and how to revoke them

Phase 1: repository and CI

  • Branch protection or a ruleset on main: pull requests required, at least one approval, required status checks, no force pushes
  • A CODEOWNERS file so reviews route to the right people automatically
  • .env.example committed, listing every variable without values; .env in .gitignore
  • Formatter and linter that run on save and in CI
  • A pre-commit secret scanner, so credentials never reach the remote in the first place
  • A CI workflow on every pull request

The pre-commit hook, using gitleaks:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1    # pin a release tag and keep it current
    hooks:
      - id: gitleaks

A CI workflow that tests against a real database rather than mocks:

name: ci
on:
  pull_request:
  push:
    branches: [main]
 
jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    services:
      postgres:
        image: postgres:18    # match your production major version
        env:
          POSTGRES_PASSWORD: postgres
        ports: ["5432:5432"]
        options: >-
          --health-cmd "pg_isready -U postgres"
          --health-interval 5s --health-timeout 5s --health-retries 10
    env:
      DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24    # your production LTS version
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm run db:migrate
      - run: npm test
      - run: npm run build

Keep CI fast enough that nobody switches to another task while waiting; when it creeps past ten minutes, parallelize or cache before it becomes normal. Our guide to CI/CD with GitHub Actions covers caching, matrices and deployment jobs in depth.

Phase 2: hosting and deploys

  • Production on a managed platform (Vercel, Render, Railway, Fly.io) or one cloud's managed container service (Google Cloud Run, AWS ECS on Fargate, Azure Container Apps)
  • Staging with its own database and its own credentials, never production's
  • Preview deployments per pull request where the platform supports them
  • Automatic deploys on merge to main; no laptop deploys
  • Rollback rehearsed: you have rolled back a real deploy and know how long it takes
  • Health endpoints, split into liveness and readiness

The split matters. Liveness answers "is the process alive"; readiness answers "can it serve requests". If a liveness check fails when the database is down, the platform restarts healthy processes in a loop and turns a database blip into a full outage.

// Liveness: the process is up. Used by the platform to decide on restarts.
app.get("/healthz", (_req, res) => res.status(200).send("ok"));
 
// Readiness: dependencies are reachable. Used by uptime checks and load balancers.
app.get("/readyz", async (_req, res) => {
  try {
    await db.query("select 1");
    res.status(200).send("ready");
  } catch {
    res.status(503).send("database unavailable");
  }
});

Deploys and migrations need an order. Use expand and contract: first deploy a migration that only adds (a new column, a new table), then deploy code that writes to both, backfill, switch reads, and only later drop the old structure. Every step is safe to roll back, which is the point.

Phase 3: database and data safety

  • Managed PostgreSQL with point-in-time recovery enabled and a retention window you chose deliberately
  • Migrations in version control, applied by the pipeline, never by hand in production
  • Connection pooling in front of the database, essential with serverless functions
  • A second, independent backup: a nightly logical dump to storage in a different account, with object lock or immutability so a compromised admin account cannot delete both copies
  • A restore drill, performed before launch and then quarterly

The drill is short. Record how long each step takes; that is your real recovery time, whatever the plan says.

pg_dump --format=custom --no-owner --file=nightly.dump "$DATABASE_URL"
createdb restore_drill
pg_restore --dbname=restore_drill --no-owner --jobs=4 nightly.dump
psql -d restore_drill -c "select max(created_at) from orders;"

Restores fail for boring reasons: a missing extension, a major version mismatch, an encryption key nobody saved. You want to discover these on a quiet Tuesday. Our disaster recovery guide covers recovery objectives and multi-region designs for when you outgrow this.

Phase 4: observability

  • Error tracking with source maps uploaded and each release tagged, so a new error points at the deploy that introduced it
  • Structured JSON logs with a request ID on every line
  • Request ID propagation across services, either your own header or W3C traceparent
  • External uptime checks on the readiness endpoint and the login page, from more than one location
  • Latency tracking at p95 for the endpoints that matter: login, the core action, checkout
  • Alerts routed to a phone, not an inbox nobody reads

A log line worth having at 2 a.m.:

{"ts":"2026-10-04T09:12:44.120Z","level":"error","msg":"billing webhook failed","request_id":"req_7f3a9c","org_id":"org_123","route":"/webhooks/billing","duration_ms":412,"error":"timeout calling provider"}

Alert on symptoms first. A starting set, with thresholds to tune for your traffic:

AlertConditionAction
Site downReadiness check fails from two or more locations, twice in a rowPage
Error rateServer errors above a set share of requests for five minutesPage
Slow core pathp95 latency on login or checkout above target for ten minutesPage
Disk fillingAbove 85% on any volumeTicket; page above 95%
Backup missedNightly dump or snapshot did not completeTicket, same day
Certificate or domain expiryLess than 14 days remainingTicket
New error type in productionFirst occurrence after a releaseChat notification

If you run your own Linux servers or VMs rather than a fully managed platform, you also need host-level visibility: disk, memory, CPU and whether your systemd services are running. We built Trinetra for exactly that: one small daemon per server with readable alert rules and Telegram alerts, no external database. For logs, metrics and traces across services, see our guide to observability in production.

Phase 5: security baseline

  • Secrets in the platform's secret store or a secrets manager, injected at runtime; never in the repository, never in container images
  • Secret scanning with push protection enabled on the code host, as a second line behind the pre-commit hook
  • No long-lived cloud keys in CI: use OIDC federation from your CI provider to AWS, GCP or Azure
  • Dependency updates automated with Dependabot or Renovate
  • Input validation at every API boundary with a schema library (Zod, Pydantic or your stack's equivalent)
  • Authorization on every object access: check that the requested record belongs to the caller's organization, not just that the caller is logged in
  • Rate limits on login, signup, password reset and any expensive endpoint
  • Security headers: HSTS, a content security policy, X-Content-Type-Options and frame restrictions
  • Email authentication for your sending domain: SPF, DKIM and DMARC
  • Audit logging turned on for your cloud accounts and admin actions
  • An offboarding checklist that revokes access across every system in the access inventory

A minimal Dependabot configuration that groups small updates so they do not flood the team:

version: 2
updates:
  - package-ecosystem: npm
    directory: "/"
    schedule:
      interval: weekly
    groups:
      minor-and-patch:
        update-types: ["minor", "patch"]
  - package-ecosystem: github-actions
    directory: "/"
    schedule:
      interval: weekly

For email, start DMARC in monitoring mode, read the reports for a few weeks, then move to quarantine and reject. Gmail and Yahoo require SPF, DKIM and DMARC from bulk senders, and unauthenticated password reset emails land in spam, which shows up as a support queue full of locked-out users.

_dmarc.yourcompany.com.  TXT  "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourcompany.com"

Phase 6: operational readiness

  • On-call: one named person each week with alerts on their phone, and a named second person to escalate to
  • Runbooks for the likely failures: database unavailable, bad deploy, a critical third party down, certificate or domain expiry, disk full, a sudden cost spike
  • An incident process: declare in a dedicated channel, one person coordinates, customers get a status update, a short postmortem follows within a week
  • A recovery document listing every service, where its configuration lives, who has access and the order in which to restore things
  • Cost anomaly alerts in addition to budgets

What can wait

Not yetWhy it can waitSignal that you need it
KubernetesManaged containers handle a handful of services with far less operational loadMany services, a platform team, and scheduling or networking needs managed services cannot meet
Multi-region active-activeData consistency across regions is hard and expensiveA contractual uptime target one region cannot meet, or data residency rules
MicroservicesA modular monolith is faster to change and debugTeam boundaries and independent scaling needs that line up
Service meshFew services means little traffic policy to manageMany services needing mutual TLS, retries and traffic shifting
Self-hosted observability stackHosted tools are cheaper than your time at small scaleIngest costs become a top budget line

One thing should not wait if you are on a major cloud: infrastructure as code. Define resources in Terraform, OpenTofu or Pulumi from the first one. On a PaaS, the platform's own configuration file in the repository is enough until you move.

What goes wrong in production

The domain or certificate expires. Auto-renew was on, the card on file was not valid, and the warning emails went to a former employee. Add expiry checks to monitoring and keep billing contacts current.

A personal account owns the root. The co-founder who created the cloud account leaves, and recovering it takes weeks of support tickets. Fix it in phase 0, when it takes ten minutes.

The backup does not restore. The snapshot exists but the restore fails on a missing extension or a lost encryption key. Only a drill finds this.

A migration locks a large table during deploy. A plain CREATE INDEX blocks writes on a busy table; use CREATE INDEX CONCURRENTLY in PostgreSQL, and run long backfills in batches outside the deploy.

Serverless connection storms. A traffic spike starts hundreds of function instances, each opening a database connection, and the database refuses new ones. Pool connections from the first deploy.

Alerts to an unread inbox. The monitoring worked and told nobody. Route pages to phones and test the route.

Staging touches production. A staging environment configured with production credentials sends real emails or charges real cards. Separate credentials per environment, and make staging's billing keys test-mode only.

The surprise bill. A runaway job, a verbose log level left on, or a public bucket serving large files. Budgets and anomaly alerts catch it in a day rather than at month end.

Cloud keys stolen from CI. A compromised dependency reads long-lived keys from the CI environment. Short-lived OIDC credentials limit what an attacker can take.

Frequently Asked Questions

What infrastructure does a startup need before launch?

Before launch, a startup needs company-owned accounts with MFA, CI on every pull request, automatic deploys with a tested rollback, a managed database with point-in-time recovery and a completed restore test, error tracking, uptime alerts, and secrets kept out of the repository. One person should be on call with alerts on their phone. Most other infrastructure can wait for a specific need.

Does a startup need Kubernetes?

Most early-stage startups do not. Managed platforms and managed container services run a handful of services with far less operational work, and Kubernetes adds upgrades, networking and security surface that someone has to own. Consider it when you have many services, a team to run the platform and requirements that simpler services cannot meet.

Do startups need a staging environment?

Yes, once real users depend on the product. Staging catches environment-specific problems such as configuration, migrations and third-party integrations before they reach customers. It needs its own database and its own credentials, and it should never be able to send real emails or charge real cards.

How should a startup manage secrets?

Store secrets in your hosting platform's secret store or a dedicated secrets manager and inject them at runtime. Never commit them to the repository or bake them into container images, and run a secret scanner in a pre-commit hook and on the code host. In CI, prefer short-lived OIDC credentials over long-lived cloud keys.

What monitoring should a startup set up first?

Start with error tracking, external uptime checks on a readiness endpoint, and alerts on error rate and latency for the most important user paths. Route those alerts to the on-call person's phone. Add structured logs with request IDs next, and host-level monitoring if you run your own servers.

How often should a startup test backups?

Restore a backup before launch and then at least every quarter, and again after any major database change such as a version upgrade. Record how long the restore took, because that is your real recovery time. Restores usually fail for mundane reasons like missing extensions or lost keys, which only a drill will reveal.

Need help getting to production?

We are senior engineers who build infrastructure and then run it: if we built it, we are on the pager. We can stand up this foundation with you, audit an existing setup against this checklist, or take an AI-built prototype to a production system that survives real traffic.