The Cloudflare registrar driver for Simple Container, extracted from the SC monolith's pkg/clouds/{cloudflare,pulumi/cloudflare} subtrees as the first out-of-tree sc-plugin per the sc-plugin-architecture design.
A plugin binary lets SC drive Cloudflare DNS + Workers edge from a stack without SC core having to vendor the (~180 MB) pulumi-cloudflare SDK. The plugin talks to SC core's running Pulumi engine over the SC plugin bus, registers resources against the SAME monitor, and shows up in state exactly as the in-tree driver did.
- What this plugin does
- Install
- Configure in
server.yaml - Build from source
- Repository layout
- Protocol
- Compatibility
- Live-smoked
- License
Everything the pre-split pkg/clouds/pulumi/cloudflare/ subtree did:
- Zone lookup — resolves the stack's
zoneNameto a Cloudflare zone ID. - DNS records — bulk at
provisiontime via thednsRecords:list; per-record on demand viaNewDnsRecordfor drivers that register endpoint DNS at resource-provisioning time. - Host-header override Workers — the full worker-script-plus-route pattern SC uses for VM-compose and k3s-cloudrun to put a custom domain in front of an origin that doesn't speak that hostname.
- Domain-for-endpoint shortcut — the common "proxied CNAME + override worker" combo in one call.
What it does NOT do (yet):
- Streaming per-record output progress — plugin currently returns URNs synchronously; progress bars don't tick during long creates. Follow-up with the Fingerprint RPC.
- Cloudflare resources beyond the registrar surface — if a future SC feature needs Pages, R2, Durable Objects, etc, add them as new resource types the plugin advertises in Schema.
Pre-built binaries are attached to every GitHub Release. Supported platforms:
| GOOS | GOARCH | Binary filename |
|---|---|---|
| linux | amd64 | sc-plugin-cloudflare-linux-amd64 |
| linux | arm64 | sc-plugin-cloudflare-linux-arm64 |
| darwin | arm64 | sc-plugin-cloudflare-darwin-arm64 |
darwin/amd64 (Intel Macs) and Windows are intentionally unsupported — the operator surface is Linux CI runners + Apple-Silicon dev laptops.
# Linux amd64 example — swap suffix for your platform / version.
curl -sSLO https://github.com/simple-container-com/sc-plugin-cloudflare/releases/download/v1.0.0/sc-plugin-cloudflare-linux-amd64
curl -sSLO https://github.com/simple-container-com/sc-plugin-cloudflare/releases/download/v1.0.0/sha256sums.txt
sha256sum -c sha256sums.txt --ignore-missing # must say "OK"
chmod +x sc-plugin-cloudflare-linux-amd64
mv sc-plugin-cloudflare-linux-amd64 /opt/sc-plugins/sc-plugin-cloudflareSC core v2026.10+ resolves plugins directly from a GitHub Release — specify a source: block in plugins: instead of hand-placing a binary (details under Configure in server.yaml). The binary is cached under ~/.sc/plugins/<repo>/<version>/ and sha256-verified on every spawn.
See Build from source below.
Add the plugin to the plugins: block and declare the registrar under resources.registrar: with type: cloudflare:
schemaVersion: 1.0
provisioner:
type: pulumi
config:
organization: your-org
state-storage: { type: s3-bucket, config: {...} }
secrets-provider: { type: awskms, config: {...} }
plugins:
cloudflare:
# Preferred: fetch from GitHub Release. SC caches + sha256-verifies.
source:
type: github-release
repository: simple-container-com/sc-plugin-cloudflare
version: v2026.10.24-4e4508d # pin a specific release
sha256: <sha256sum of the binary> # optional — defaults to the release's sha256sums.txt
# Dev / air-gapped alternative (mutually exclusive with source):
# path: /opt/sc-plugins/sc-plugin-cloudflare
resources:
registrar:
type: cloudflare
config:
zoneName: your-zone.example.com # the Cloudflare zone this stack serves
accountId: ${env:CF_ACCOUNT_ID} # the account that owns the zone
credentials: ${secret:CF_API_TOKEN}
dnsRecords:
- name: www
type: A
value: 203.0.113.42
proxied: true
resources:
staging:
resources: {}The credentials value is a Cloudflare API token (not a legacy global API key) with at least:
Zone:Zone:Readon the specific zone (forLookupZone),Zone:DNS:Editon the specific zone (for record / worker-route creation),Account:Workers Scripts:Editon the account (for worker-script creation).
The accountId field is required and must match the account the zone lives in — the plugin pins LookupZone with Account.Id + Match: "all" to resolve same-named zones deterministically.
Multiple Cloudflare accounts can own a zone with the same name (e.g. a migrated zone). This plugin fails fast if LookupZone returns multiple results for (zoneName, accountId). If you see "failed to find exactly one result: N found", you have N zones in that account with the same name — delete or rename the duplicates in Cloudflare's dashboard.
git clone https://github.com/simple-container-com/sc-plugin-cloudflare.git
cd sc-plugin-cloudflare/plugin
go build -o /tmp/sc-plugin-cloudflare ./cmd/sc-plugin-cloudflareResulting binary is ~178 MB (vendors pulumi-cloudflare SDK v6.17.0). Peak RSS during build is ~2 GB — well below SC core's.
Requires Go 1.23+. The build does not need the SC core repo cloned; dependencies resolve through go.mod.
sc-plugin-cloudflare/
api/ # Go module — config structs for SC core
cloudflare.go # AuthConfig + RegistrarConfig
init.go # api.RegisterProviderConfig for "cloudflare"
cloudflare_test.go # trivial unit tests
go.mod # imports: SC's pkg/api only. NO pulumi-cloudflare.
plugin/ # Go module — the plugin binary
cmd/sc-plugin-cloudflare/
main.go # SCCloudPluginServer implementation
internal/
registrar.go # lifted driver code — the Pulumi registrar
ips.go # Cloudflare edge IP range lookup
go.mod # imports: pulumi-cloudflare SDK + SC plugin runtime
LICENSE # MIT
CODEOWNERS
CLAUDE.md # notes for AI coding agents
README.md # this file
.github/workflows/ # (TODO) release automation
Two Go modules. SC core imports /api at build time to register the "cloudflare" type in its config-tier map and generate schemas; the /plugin module and its heavy pulumi-cloudflare SDK dependency never cross that boundary. Same pattern D3 in the design doc fixes for the whole fleet of future plugin repos.
The plugin advertises on the SC plugin bus v1:
SchemaResponse {
plugin_version: "cloudflare-0.0.1"
registrar_types: ["cloudflare"]
pulumi_plugins: [{ package: "cloudflare", version: "6.17.0" }]
}Implements these RPCs from SCCloudPlugin:
| RPC | What it does |
|---|---|
Schema |
Advertise the surface above. |
ProvisionRegistrar |
Spin up the Cloudflare Pulumi provider resource + look up the configured zone. Return main_domain. |
NewDnsRecord |
Register one DNS record. Called by other SC drivers that need endpoint DNS. |
NewOverrideHeaderRule |
Register a Worker script + Workers route for host-header override. Used by vm-compose + k3s-cloudrun. |
ProvisionDomainForEndpoint |
Combine NewDnsRecord (proxied CNAME) + NewOverrideHeaderRule for a cloud endpoint. |
PreDestroy |
No-op for this driver — Pulumi's own cloudflare provider handles cleanup. |
Each RPC opens a short-lived pulumi.Context against req.Header.MonitorAddress and reuses the lifted internal/registrar.go code unchanged. The plugin never writes to SC's Pulumi state directly — resources are registered against SC core's monitor via the MonitorProxy.
| sc-plugin-cloudflare | SC core (simple-container-com/api) |
Pulumi SDK | pulumi-cloudflare |
|---|---|---|---|
| v0.0.x (initial) | main after PR #482 lands | sdk/v3 v3.245.0 | v6.17.0 |
The plugin binary vendors its own pulumi-cloudflare SDK; SC core's version doesn't matter after this plugin is pinned. SC core's pulumi/sdk/v3 version MUST match the plugin's — a mismatch breaks the shared monitor gRPC protocol. Pin both via the plugin's /plugin/go.mod and SC core's go.mod.
2026-10-24, locally built sc against ai-taskmanager.space (Cloudflare zone e41f418f1202c8a31a4cea83969975cb):
$ sc provision -s smoke-cf --skip-preview
+ pulumi:pulumi:Stack: (create)
+ pulumi:providers:cloudflare: (create)
apiToken : [secret]
version : "6.17.0"
Resources: + 2 created, Duration: 1s
$ echo Y | sc destroy -s smoke-cf --parent --skip-preview --skip-refresh
Removed stack: all resources have been destroyed
The provider resource at
urn:pulumi:smoke-cf::sc-plugin-smoke::pulumi:providers:cloudflare::<account-id>
was created by the plugin subprocess talking to SC core's Pulumi monitor via the MonitorProxy; SC core's pkg/clouds/pulumi/cloudflare subtree was not engaged. Full write-up: Addendum E in 04-no-destroy-invariant.md.
See CLAUDE.md for notes to AI coding agents (and humans) — architecture context, the two-module shape, the four bugs the extraction smoke caught + how the code defends against them, and the live-smoke pattern.
Commits must carry a DCO sign-off (git commit -s). Do not add Co-Authored-By trailers.
MIT.