Skip to content

Greenfield Setup

Starting fresh with Tamer — no existing Cloudflare resources, no legacy naming. This is the happy path from empty repo to a live multi-tenant deployment.

1. Install Tamer

bash
npm install -D @dragonmastery/tamer wrangler

2. Create your config

Minimum viable config for a single-product multi-tenant app:

ts
// tamer/project.config.ts
import { cf, defineConfig } from "@dragonmastery/tamer";

export default defineConfig({
  stack: "myapp",
  account_id: "your-account-id",
  compatibility_date: "2025-05-19",

  // Workers for Platforms: dispatch namespace per product-env (default
  // name `{product}-{env}`). Each service declared under `workers` gets
  // one script per workspace (`{service}-{workspace}-{env}`).
  wfp: {
    ephemeralEnvPattern: "^pr-",
    namespaces: {
      myapp: {
        workers: {
          tenant: {
            main: "apps/tenant/src/index.ts",
            shardGroups: [
              { name: "primary", binding: "DB", migrationsDir: "db/primary/migrations" },
            ],
            d1: [
              { logicalName: "system", binding: "DB_SYSTEM", migrationsDir: "db/system/migrations" },
            ],
          },
        },
      },
    },
  },

  workers: {
    api: {
      path: "apps/api",
      main: "src/index.ts",
      scriptName: "myapp-api",
      resources: {
        d1: [{
          logicalName: "app-db",
          type: "single",
          binding: "APP_DB",
          migrationsDir: "apps/api/migrations",
        }],
      },
      vars: { ENVIRONMENT: "local" },
      env: {
        dev: { vars: { ENVIRONMENT: "${tamer:env}" } },
        prod: { vars: { ENVIRONMENT: "prod" } },
      },
      tamerRoutes: [
        { host: "api.myapp.com", customDomain: true },
      ],
    },
    spa: {
      path: "apps/spa",
      scriptName: "myapp-web",
      assets: { directory: "dist", not_found_handling: "single-page-application" },
      build: { command: "vite build" },
      vars: {
        VITE_API_URL: "http://127.0.0.1:8993/v1",
        ENVIRONMENT: "local",
      },
      env: {
        dev: { vars: { VITE_API_URL: "https://${tamer:env}.api.myapp.com/v1", ENVIRONMENT: "${tamer:env}" } },
        prod: { vars: { VITE_API_URL: "https://api.myapp.com/v1", ENVIRONMENT: "prod" } },
      },
      tamerRoutes: [
        { host: "myapp.com", customDomain: true },
      ],
    },
  },

  outputs: {
    api_worker_name: cf.worker("api").name,
  },
});

For the full split-config pattern (per-worker files with env overrides), see Single-Product Multi-Tenant.

3. Set up .env

bash
# .env (gitignored)
CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_API_TOKEN=your-api-token

That's it — no app config values, no VITE_* URLs. Just Tamer's auth credentials. See Values Lifecycle.

4. Bootstrap (once per account)

bash
bunx tamer bootstrap

Creates three shared account-scoped resources:

  • tamer-state (D1) — deployment state for all envs
  • tamer-secrets (D1) — encrypted secrets vault
  • tamer-artifacts (R2) — build bundles

Idempotent — safe to run multiple times.

5. Initialize secrets

bash
# Generate master key for dev (shown once — store in CI + password manager)
bunx tamer secrets init --env dev

# Set each required secret (pipe the value on stdin — interactive entry
# is not supported yet, so a bare `set` on a TTY errors)
echo -n "sk_live_..." | bunx tamer secrets set STRIPE_API_KEY --env dev
echo -n "..."        | bunx tamer secrets set JWT_SECRET --env dev

# Verify all declared secrets are in the vault
bunx tamer secrets verify --env dev

Store the master key as TAMER_SECRETS_KEY_dev in your CI secrets.

6. Apply resources

bash
bunx tamer apply --env dev

Creates everything declared in config:

  • Dispatch namespace (myapp-dev, one per wfp.namespaces product)
  • D1 databases (e.g. db_app-db_devdb_{logical}_{env})
  • R2 buckets
  • KV namespaces
  • DNS records / routes
  • Generates wrangler.json per worker

7. Migrate databases

bash
bunx tamer migrate --env dev

Runs D1 migrations on each database declared with migrationsDir.

8. Deploy workers

bash
bunx tamer deploy --env dev

Per worker (topologically sorted by service bindings):

  1. Generate wrangler.json (if changed)
  2. Build SPA (vite build — values baked at compile time)
  3. wrangler typeswrangler deploy
  4. Push secrets from vault
  5. Register workflows

9. Provision your first tenant

bash
# `--main` is optional — defaults to the template's configured `main`.
bunx tamer wfp tenant provision --env dev \
  --workspace acme

Creates per-tenant D1 shards, runs migrations, bundles the tenant Worker via wrangler (TS compilation, WASM, polyfills), and deploys it to the dispatch namespace with D1 bindings. See Single-Product Multi-Tenant for the dispatch routing pattern.

10. Set up CI

Copy the workflow templates from CI Workflows into .github/workflows/. Set up GitHub secrets:

  • CLOUDFLARE_API_TOKEN
  • CLOUDFLARE_ACCOUNT_ID (variable)
  • TAMER_SECRETS_KEY_dev
  • TAMER_SECRETS_KEY_prod

Recap

bootstrap          → once per account
secrets init       → once per env (generates master key)
secrets set        → per secret
apply              → creates resources
migrate            → runs D1 migrations
deploy             → builds + deploys workers
wfp tenant provision → creates a tenant

Next: Single-Product Multi-Tenant for the full architecture, CI Workflows to automate everything, or Local Development for the daily dev loop.

Released under the Tamer Evaluation License.