Skip to content

feat(infra): demo.pywire.dev DNS record - #18

Merged
reecelikesramen merged 3 commits into
mainfrom
claude/project-thread-3ala5u
Sep 30, 2026
Merged

reecelikesramen merged 3 commits into
mainfrom
claude/project-thread-3ala5u

Conversation

@reecelikesramen

@reecelikesramen reecelikesramen commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Before: demo.pywire.dev doesn't exist. There's no DNS record, no Workers and no routes.

After: Terraform creates the demo Workers (pywire-demo for the landing page, pywire-demo-edge-stateless and pywire-demo-form-builder), their routes, and demo.pywire.dev's DNS record. The record is a proxied AAAA 100::, Cloudflare's placeholder for a Worker-only hostname. demo.pywire.dev/* goes to the landing page, and demo.pywire.dev/<name> and /<name>/* go to that example's Worker.

How: cloudflare_worker is a Worker without code, the same way the Pages projects here exist without their deployments. pywire/pywire's Deploy Examples workflow (pywire/pywire#392) uploads each Worker's code and vars. Secrets are set with wrangler secret put so they stay out of state. workers.dev and preview URLs are off, matching each wrangler.toml. Cloudflare picks the most specific route, so the example routes win over the landing page's catch-all. Adding an example later means adding its name to local.demo_examples.

Order: apply this first, then merge pywire/pywire#392 (its first run uploads the code), then set the secrets.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Terraform plan — 📝 changes

cloudflare_r2_bucket.cdn: Refreshing state... [id=pywire-cdn]
cloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=ba2224a5004345f19ad66c5a71343976]
cloudflare_dns_record.vscode_verification: Refreshing state... [id=8ac56f7d47b8d2401df848706ad88cdd]
cloudflare_email_routing_address.destinations["[email]"]: Refreshing state... [id=b465118aef02490e9f584146ce54f65b]
cloudflare_dns_record.docs_cname: Refreshing state... [id=e562a5382eacb08985eaba0d4907fb2c]
cloudflare_ruleset.allow_llm_crawlers: Refreshing state... [id=375241922de4405a8b77b812e6e25730]
cloudflare_pages_project.docs: Refreshing state... [id=pywire-docs]
cloudflare_email_routing_settings.main: Refreshing state... [id=9f1c5e8c997d29163e81a24f7b26dc35]
cloudflare_pages_project.landing: Refreshing state... [id=pywire-landing]
cloudflare_email_routing_rule.individual_aliases["hello"]: Refreshing state... [id=1ff2a9d2add740089d4c7952455e4b56]
cloudflare_email_routing_rule.maintainers_group: Refreshing state... [id=2f20558a55f04e10959ead934f397730]
cloudflare_email_routing_rule.individual_aliases["reece"]: Refreshing state... [id=d14078e65aea4106adf662dc46d54710]
cloudflare_pages_domain.docs: Refreshing state... [id=docs.pywire.dev]
cloudflare_workers_script.router: Refreshing state... [id=pywire-router]
cloudflare_pages_domain.landing: Refreshing state... [id=pywire.dev]
cloudflare_dns_record.nightly: Refreshing state... [id=6a2d6b6878cc8452e1e29dd1f30c6dd1]
cloudflare_workers_route.nightly: Refreshing state... [id=874d0be239a04de79226d64cd941e9bf]
cloudflare_workers_route.catch_all: Refreshing state... [id=a70946d772474786ada02b87b9fd05cf]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # cloudflare_dns_record.demo will be created
  + resource "cloudflare_dns_record" "demo" {
      + content          = "100::"
      + created_on       = (known after apply)
      + id               = (known after apply)
      + meta             = (known after apply)
      + modified_on      = (known after apply)
      + name             = "demo"
      + proxiable        = (known after apply)
      + proxied          = true
      + settings         = (known after apply)
      + tags             = (known after apply)
      + tags_modified_on = (known after apply)
      + ttl              = 1
      + type             = "AAAA"
      + zone_id          = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_worker.demo["edge-stateless"] will be created
  + resource "cloudflare_worker" "demo" {
      + account_id     = "abd8226d8d910afcfa1d370097e6336a"
      + created_on     = (known after apply)
      + id             = (known after apply)
      + logpush        = false
      + name           = "pywire-demo-edge-stateless"
      + observability  = {
          + enabled            = false
          + head_sampling_rate = 1
          + logs               = {
              + enabled            = false
              + head_sampling_rate = 1
              + invocation_logs    = true
            }
        }
      + references     = (known after apply)
      + subdomain      = {
          + enabled          = false
          + previews_enabled = false
        }
      + tags           = []
      + tail_consumers = []
      + updated_on     = (known after apply)
    }

  # cloudflare_worker.demo["form-builder"] will be created
  + resource "cloudflare_worker" "demo" {
      + account_id     = "abd8226d8d910afcfa1d370097e6336a"
      + created_on     = (known after apply)
      + id             = (known after apply)
      + logpush        = false
      + name           = "pywire-demo-form-builder"
      + observability  = {
          + enabled            = false
          + head_sampling_rate = 1
          + logs               = {
              + enabled            = false
              + head_sampling_rate = 1
              + invocation_logs    = true
            }
        }
      + references     = (known after apply)
      + subdomain      = {
          + enabled          = false
          + previews_enabled = false
        }
      + tags           = []
      + tail_consumers = []
      + updated_on     = (known after apply)
    }

  # cloudflare_worker.demo["site"] will be created
  + resource "cloudflare_worker" "demo" {
      + account_id     = "abd8226d8d910afcfa1d370097e6336a"
      + created_on     = (known after apply)
      + id             = (known after apply)
      + logpush        = false
      + name           = "pywire-demo"
      + observability  = {
          + enabled            = false
          + head_sampling_rate = 1
          + logs               = {
              + enabled            = false
              + head_sampling_rate = 1
              + invocation_logs    = true
            }
        }
      + references     = (known after apply)
      + subdomain      = {
          + enabled          = false
          + previews_enabled = false
        }
      + tags           = []
      + tail_consumers = []
      + updated_on     = (known after apply)
    }

  # cloudflare_workers_route.demo["edge-stateless"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/edge-stateless"
      + script  = "pywire-demo-edge-stateless"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["edge-stateless/*"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/edge-stateless/*"
      + script  = "pywire-demo-edge-stateless"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["form-builder"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/form-builder"
      + script  = "pywire-demo-form-builder"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["form-builder/*"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/form-builder/*"
      + script  = "pywire-demo-form-builder"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

  # cloudflare_workers_route.demo["site"] will be created
  + resource "cloudflare_workers_route" "demo" {
      + id      = (known after apply)
      + pattern = "demo.pywire.dev/*"
      + script  = "pywire-demo"
      + zone_id = "9f1c5e8c997d29163e81a24f7b26dc35"
    }

Plan: 9 to add, 0 to change, 0 to destroy.

@reecelikesramen
reecelikesramen marked this pull request as ready for review September 30, 2026 00:59
@reecelikesramen
reecelikesramen merged commit 89c2683 into main Sep 30, 2026
3 of 4 checks passed
reecelikesramen added a commit to pywire/pywire that referenced this pull request Sep 30, 2026
Needs #400 (merged): the example Workers use `main = "src/entry.py"`,
which #400 teaches `pywire build` to keep. The Workers themselves, their
routes and DNS are created by pywire/pywire.dev#18.

Before: none of the examples ran anywhere public.

After: demo.pywire.dev has a landing page listing the demos, the form
builder at demo.pywire.dev/form-builder/, and the stateless demo at
demo.pywire.dev/edge-stateless/. Every push to main that touches those
examples or pywire itself redeploys them, so the demos always run main.

Each example deploys from its own folder, the same way a
create-pywire-app project would. There is no staging step. Examples
aren't released, so release-please isn't involved.

How:
- Each example has a committed `wrangler.toml` (Worker
`pywire-demo-<name>`, `main = "src/entry.py"`,
`PYWIRE_BASE_PATH=/<name>`) with code and vars only. Terraform in
pywire.dev creates the Worker and its routes, the way it creates the
docs Pages project.
- `pywire[cli]` sits in each example's dev group, so the Worker bundle
holds only runtime dependencies.
- `.github/workflows/deploy-examples.yml` runs on pushes to main (paths
filtered) and by hand. It deploys `examples/demo-site` with wrangler.
For each example it builds the client bundle, runs `uv run pywire build
--platform cloudflare-edge`, then `uv run --with workers-py pywrangler
deploy`.
- `UV_NO_EDITABLE=1` makes pywrangler vendor a real copy of the
workspace's pywire rather than an editable link, so the demos run main's
source.
- The form builder gets `CLIENT_IP_HEADER=cf-connecting-ip`, which
Cloudflare sets itself.
- realtime waits for the shared Durable Object target (#393). taskboard
needs a database, so it's left out.

Order:
1. Merge pywire.dev#18 and run Infra apply. That creates the Workers,
routes and DNS.
2. Reece gives this repo's `CLOUDFLARE_API_TOKEN` the account-level
Workers permission at Editor.
3. Merge this. The first run uploads the code.
4. Reece sets the Worker secrets: `PYWIRE_SECRET_KEY` on both example
Workers, plus `GROQ_API_KEY` and `TYPESAFE_API_KEY` on
`pywire-demo-form-builder` (commands in `examples/README.md`). The
examples return errors until then.

Worth knowing: the form builder's rate limiter keeps its counts in
memory. On Workers each isolate has its own memory, so a visitor can get
more than 10 builds per 10 minutes. The out-of-usage message still
covers the case where the keys run dry.

Tests: in examples/form-builder I ran the build and `UV_NO_EDITABLE=1 uv
run --with workers-py pywrangler dev` in local workerd. The page, CSS
and client loaded under `/form-builder/`, stateless POSTs returned 200,
and a build with a fake key showed the out-of-usage message. A dry-run
upload is 2.5 MB gzipped. The Examples check passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: reecelikesramen <3149587+reecelikesramen@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants