Alchemist Operations

Alchemist: Deploys and Domains

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

|View as Markdown
Hunter Hodnett
Hunter HodnettCPTO at Chipp
|1 min read
#alchemist#platform#deploys-and-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.

yaml
# .alchemist/deployment.yaml
resourceClass: medium      # default: small
replicas: 2                # default: 1
env:
  LOG_LEVEL: info
  FEATURE_X: enabled
FieldValuesNotes
resourceClasssmall (default), medium, largexl and xxl need approval first: email support@chipp.ai.
replicas1 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.
envup to 50 keys, 4KB eachApplied 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:

yaml
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.

Pick a subdomain

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.

Open the Domains tab

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.

Enter your hostname

Type the hostname you chose, for example:

plaintext
app.example.com

Do not include https:// (or any path).

Add the routing CNAME

At your existing DNS host, add:

TypeName / HostTarget / Value
CNAMEyour 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.

Add the DCV/SSL validation CNAME

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.

Click Verify DNS

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

StepTypeName / HostTarget / Value
RoutingCNAMEyour subdomain label (e.g. app)fallback.chipp.ai
DCV / SSL validationCNAMEunique, shown in the Alchemist UIunique, 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.