Alchemist: Deploys and Domains
How deployments work on the Alchemist platform and how to attach custom domains.

Every project has a Live URL on the platform’s shared domain (for
example your-project.on.chipp.ai). You can also attach one or more custom
domains from the Domains tab.
How deploys happen
A deploy is triggered automatically whenever:
- The agent’s branch merges into your project’s default branch.
- You click Redeploy in the project’s detail view.
- You push a commit to the default branch yourself, directly to the platform-hosted repo — no GitHub account, and no need to eject first. See Clone Your App’s Code and Develop Locally.
- After ejecting your repo to your own GitHub org, your own CI pushes a commit and calls the platform’s deploy API with a deploy token.
Each deploy runs a build sandbox that produces a container image, then the platform’s rollout controller pushes that image to the live pod behind the same URL. Crossover is a few seconds; in-flight requests finish on the old pod.
Deploy pacing: rapid pushes coalesce
Pushes are never rejected and never queue up into a backlog of rollouts. Every push is accepted immediately, and if another deploy for the same project is already waiting, the newer one replaces it: only the newest commit gets built. The older row stays visible in the deploys list marked superseded, with a pointer to the deploy that replaced it, so you can see what happened rather than watching a push disappear.
Consecutive deploys for one project are spaced by a short window (about five minutes). Push three times in two minutes and you get one build of the newest commit, not three builds. Because the newest commit already contains the earlier ones, nothing is lost.
A failed build does not consume that window, so a retry (or a fix you push straight after a failure) is picked up right away instead of waiting.
Deployment configuration
Everything about how your app runs is controlled by an optional
.alchemist/deployment.yaml file in your repo. Omit the file, or any field in
it, and you get the defaults.
# .alchemist/deployment.yaml
resourceClass: medium # default: small
replicas: 2 # default: 1
env:
LOG_LEVEL: info
FEATURE_X: enabled| Field | Values | Notes |
|---|---|---|
resourceClass | small (default), medium, large | xl and xxl need approval first: email support@chipp.ai. |
replicas | 1 to 5 self-serve (default 1) | Above 5 total pods needs approval: email support@chipp.ai with your expected concurrency profile. Each pod is billed while it runs. |
env | up to 50 keys, 4KB each | Applied after the platform’s own variables, so your value wins on a name collision. |
The concurrency threshold counts total pods, so a worker block counts
toward it: replicas: 4 plus a worker is 8 pods and needs approval, while
replicas: 2 plus a single-replica worker is 3 and does not.
The schema is strict: an unknown field fails the build rather than being ignored, so a typo surfaces immediately instead of silently doing nothing.
Anything that affects security posture (service account, secret references, network policy, namespace) is deliberately not settable from this file. Those are platform-controlled, so a bad or malicious push can never request elevated access for itself.
Background workers
By default one pod both serves your traffic and runs everything else your app
does. If your app has real background work (scheduled jobs, a queue consumer, a
long poll) you can move it into its own pod by declaring a worker block:
worker:
replicas: 1 # default: same as `replicas`Your traffic pod then receives WORKER_ROLE=api and the worker pod receives
WORKER_ROLE=worker, both running the same image.
Your app has to honor WORKER_ROLE before you declare this. Both pods run
the same code, so if your background work is not gated on that variable it will
run in both pods: every scheduled job fires twice, every queue is consumed
twice. Gate the work on WORKER_ROLE === "worker", and treat the variable being
absent as “run everything” so the app still behaves correctly when deployed
without a worker.
Apps scaffolded from an Alchemist template now ship this gating already: a
src/lib/worker-role.ts helper plus every background starter wrapped in
if (runsBackgroundWork). If your repo has that file, you can declare a worker
as-is.
If your repo was forked from a template before that landed, it will not have
the file, and the gating did not reach you retroactively. Check for
src/lib/worker-role.ts first: if it is missing, port the gating before
declaring a worker, or your background work runs in both pods.
Why bother: a deploy restarts the pod it rolls, so background work sharing the traffic pod gets interrupted on every deploy, and a long job competes with request latency. A separate worker pod also gets a much longer shutdown grace so in-flight work can finish, and it rolls without touching the pod serving your users.
A worker is an additional running pod and is billed as one, on the same CPU and memory basis as your app pod.
Force a redeploy
If your Live URL stops responding or you want to ship a config change without a code change, open the project and click Redeploy. The platform resolves the current HEAD SHA of your default branch, enqueues a fresh deploy at that SHA, and returns the existing in-flight deploy if one is already running (so the button is safe to click twice).
Auto-retry on transient failures
Builds and rollouts sometimes fail for reasons that have nothing to do with your code — registry rate-limits, transient auth blips, a flapping upstream mirror. The platform automatically retries a failed deploy up to two times (three attempts total) before surfacing the failure to you.
While the auto-retry is in flight you’ll see an amber banner: “Build failed — auto-retrying. No action needed.” If all three attempts fail, a manual Retry Deployment button appears. At that point the failure is usually something the agent can fix — open a ticket describing the error.
Custom domains
Every project can serve traffic on a domain you already own, in addition to (or instead of) buying one through the platform. Open the Domains tab to see both paths side by side:
- Purchase a domain — buy a new domain through the platform. Domains are billed at the registrar’s wholesale price plus a flat platform markup; the price you pay is shown at purchase and frozen on your invoice — future markup changes never re-bill historical purchases.
- Connect existing domain or subdomain — bring a domain or subdomain you already own by adding two DNS records at your current DNS host. This is the path most teams want, and it’s covered in detail below.
The platform handles certificate issuance and renewal automatically for domains added through either path.
Connecting a domain or subdomain you already own
You do not change your domain’s authoritative nameservers, and you never delegate them to Chipp. Your domain keeps whatever nameservers (and DNS host) it uses today — you’re just adding two records there.
We recommend connecting a subdomain, such as app.example.com, rather
than your apex/root domain (example.com). A subdomain keeps your main
website on its existing setup and makes the DNS records below simpler to
add without touching anything else on the domain.
From your project, open the Domains tab and click Connect existing domain or subdomain. If you have more than one project, you’ll be prompted to pick which project this domain should route to.
Type the hostname you chose, for example:
app.example.comDo not include https:// (or any path).
At your existing DNS host, add:
| Type | Name / Host | Target / Value |
|---|---|---|
| CNAME | your subdomain label (e.g. app) | fallback.chipp.ai |
Using Cloudflare for DNS? Set this record’s proxy status to DNS only (gray cloud), not proxied (orange cloud). A proxied record breaks routing and certificate validation.
Once you register the hostname in step 3, Alchemist generates a second, unique DCV (domain control validation) CNAME record for that hostname. Copy its Name and Value exactly as shown in the UI — this record is unique per domain, so don’t reuse one from another domain or guess at the value.
Same rule applies here: if you’re on Cloudflare, this record must also be DNS only (gray cloud), never proxied.
Alchemist checks both records and, once they resolve correctly, issues the certificate automatically. There’s no separate “activate HTTPS” step — certificate issuance and renewal both happen automatically once verification succeeds.
DNS records at a glance
| Step | Type | Name / Host | Target / Value |
|---|---|---|---|
| Routing | CNAME | your subdomain label (e.g. app) | fallback.chipp.ai |
| DCV / SSL validation | CNAME | unique, shown in the Alchemist UI | unique, shown in the Alchemist UI |
Propagation
DNS changes typically propagate within minutes, but can take up to 48 hours depending on your provider and the record’s TTL. If Verify DNS doesn’t succeed right away, wait a bit and try again before troubleshooting further.
Troubleshooting
- Verify DNS fails / CNAME not found — double-check the routing record’s
Name matches your subdomain label and the Value is exactly
fallback.chipp.ai(no trailing dot mismatch, no typo). Confirm you added it at the DNS host your domain actually uses. - Record present but still failing on Cloudflare — confirm both the routing and DCV records are set to DNS only (gray cloud). A proxied (orange cloud) record looks correct in the DNS panel but fails validation.
- It’s been a while and it’s still not verifying — propagation can take up to 48 hours depending on your provider and TTL; use a DNS lookup tool to confirm the records are visible from outside your network before assuming something is misconfigured.
- HTTPS still shows as pending — this is expected until both DNS records are verified and the DCV/SSL validation record has propagated. HTTPS activates automatically right after that; there’s no manual step to trigger it.