Skip to content

Deploy

Atoms are deployed directly to your Cloudflare account, by the vendor/bin/atoms CLI that ships with atoms/cli. It drives the Wrangler installed in your Worker project; it never fetches a toolchain of its own, and never stores a Cloudflare credential.

Follow Initialize the project to set up atoms-worker/, and run npm ci inside it on every fresh checkout — nothing does that for you, and a missing Wrangler surfaces as ATOMS-E073.

Deployment targets an environment named in atoms.json, and every command on this page takes --env <name> to select one. See Configuration for what an entry holds and what --env resolves from it.

worker_name is required and identifies the Worker Wrangler receives. The callback URL resolves in one order, the same one atoms dev uses: --callback-url, then ATOMS_CALLBACK_URL in the environment this command was started with, then in .env.atoms.<name> beside atoms.json, then the optional callback_url on the selected environment block. The nearer source wins silently — nothing is compared, and no combination is an error. A whole-value ${ENV_VAR} reference in the file is expanded when the deploy runs, and only when no nearer source supplied anything; an unset or empty reference is then ATOMS-E070. With no source at all, callbacks are unavailable: deploy warns and sends no callback variable.

Before it builds or ships anything, deploy prints what it resolved and where each value came from:

Environment: production
Worker: my-app (atoms.json)
Account: cf-account-1234 (caller environment: CLOUDFLARE_ACCOUNT_ID)
API token: (hidden) (.env.atoms.production: CLOUDFLARE_API_TOKEN)
Callback: https://example.com/atoms/callback (atoms.json "environments.production.callback_url")
Debug routes: disabled (atoms.json "debug_endpoints")
Serving: atoms.example.com (atoms.json "routes"/"custom_domains")

Read that table when a deployment does something you did not expect. It is the answer to “why that callback URL”, and the API token is the one value it names without showing.

On your own machine, authenticate with the installed Wrangler:

Terminal window
cd atoms-worker
./node_modules/.bin/wrangler login
cd ..

When CLOUDFLARE_API_TOKEN is unset, Wrangler uses that saved login session.

For headless or scripted deploys, supply an API token instead — a CI runner has no login session to fall back on. In CI, set CLOUDFLARE_API_TOKEN as a job variable from your secret store. Locally, put it in .env.atoms.<environment> beside atoms.json, for the target you are deploying:

Terminal window
# .env.atoms.production — gitignored by `atoms init`
CLOUDFLARE_API_TOKEN=

A token needs permission to edit Workers Scripts in the target account. Never commit it. Atoms passes both CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID into the Wrangler child process and nowhere else: never to a file, a log, or the command line.

Check your host adapter is ready first — Before you deploy lists what it must provide.

Terminal window
vendor/bin/atoms deploy --env production

deploy validates and bundles your Atom code and dependencies, then deploys the Worker through Wrangler. Use atoms build to produce a bundle for inspection or later deployment.

You can validate without a build with atoms validate. Pass the --json flag for JSON output.

The build resolves the packages listed in atoms-composer.json with composer install --no-scripts --no-plugins in an isolated directory, writes the result back to atoms-composer.lock for reproducibility, and caches it under .atoms/vendor-cache. Builds are deterministic and never execute your code.

A deployed Worker needs ATOMS_SHARED_SECRET before it will serve anything but /healthz, and it must be set after the first deploy because the Worker has to exist first. If your Atoms call app() or dispatch(), they also need ATOMS_CALLBACK_URL.

See Secrets and authentication for both kinds of secret and the rotation runbook, and Callback URL for local and deployed callback configuration.

A deploy is not immediately visible everywhere. Cloudflare propagates it over time, and an Atom already resident in memory keeps running the bundle it activated with until it next activates. List the uploaded Worker versions with:

Terminal window
vendor/bin/atoms status --env production

Verify the new Atom methods are available before deploying application code that calls them. To move a Worker back to an earlier version, see Rollback.

status lists Worker version data from Wrangler. It does not infer or print a Worker endpoint from atoms.json; set the application’s ATOMS_ENDPOINT independently.

When updating your Atoms PHP packages, upgrade the Worker runtime to the matching release. For the 0.6.0 release:

Terminal window
npm exec --yes --package=@atomsphp/runtime-cloudflare@0.6.0 -- \
atoms-runtime-cloudflare upgrade atoms-worker
cd atoms-worker
npm ci
cd ..

Use the version printed by atoms init or the version-mismatch error for your installed CLI.

Files What an upgrade does
wrangler.jsonc Preserves your configuration. Apply any required changes described in the release notes.
Runtime files listed in atoms-runtime.json Replaces them with the release’s copies and removes files the release no longer ships. Local edits are overwritten.

atoms dev and atoms deploy require an exact version match between the CLI and atoms-runtime.json, checked before building (ATOMS-E108).

The deploy Action installs dependencies in atoms-worker/, builds, and deploys using GitHub Secrets:

jobs:
deploy-atoms:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: AtomsPHP/atoms/action@v0.6.0
with:
environment: production
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare-account-id: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
shared-secret: ${{ secrets.ATOMS_SHARED_SECRET }}

Use a release tag or commit SHA matching your runtime. Set worker-directory if your Worker is in another directory. The Action’s README documents every input, how to scope the API token, and a troubleshooting table for the errors a runner hits.

The shared-secret input sets the Worker secret after deployment. It skips an existing secret by name, even if the supplied value differs. Your application needs the same value configured through its own deployment.

For rotation, first prepare the application and Worker overlap described in Rotate the shared secret. Then use rotate-shared-secret: true with shared-secret set to the new value and shared-secret-previous to the old value. On a later run, retire-shared-secret-previous: true removes the Worker overlap; remove it from the application separately.