HelixML

Host a project as a web service

Turn a Helix project into a live website or API with automatic deployment, custom domains, and session preview sharing.

Host a project as a web service

Helix can run a persistent web process from your project's source repository and make it available at a public URL. Enable the web service, add a .helix/startup.sh script, and Helix handles provisioning, deployment, and routing.

Separately, any active agent session can expose a port as a shareable preview URL — useful for checking or embedding what an agent is building before it is merged.

Enable the web service

Open your project's settings and select Web Service from the sidebar (the globe icon, or append ?tab=web-service to the URL).

Toggle Enable to turn on hosting. Set the Container port to the port your application will listen on, then save.

Once enabled, a default subdomain is created automatically. This subdomain is always listed as Live and cannot be deleted.

Write a startup script

Helix launches your application by executing .helix/startup.sh in the root of your repository. This script must start a long-running process that listens on the configured port.

The port is passed to the script as the HELIX_WEB_SERVICE_PORT environment variable:

#!/usr/bin/env bash
set -e
python3 -m http.server "$HELIX_WEB_SERVICE_PORT"

The script becomes the web server process — keep it running rather than exiting after startup.

Deploy your application

Automatic deployment on push

Every push to the default branch of the project's primary repository triggers a redeploy. No webhook configuration is needed — Helix's hosted git server fires the hook automatically after a successful push:

git push helix-origin main

The deployment sequence is:

  1. Provision a fresh persistent sandbox.
  2. Clone the repository and check out the pushed commit.
  3. Execute .helix/startup.sh with HELIX_WEB_SERVICE_PORT set.
  4. Poll the configured port until the application responds (up to 90 seconds).
  5. Atomically switch traffic to the new sandbox and stop the previous one.

If the readiness check times out, the previous sandbox continues serving traffic and the failed sandbox stays running so you can inspect it.

Manual deployment

Use Deploy Now in the Web Service tab to redeploy from the current default branch without pushing new code.

For external CI pipelines or GitHub webhooks, trigger a deploy via the API:

POST /api/v1/projects/{project_id}/web-service/deploy

Add a custom domain

In the Web Service tab, enter a domain in the Add domain field and submit the form. Helix displays a CNAME record to add at your DNS provider:

FieldValue
NameYour subdomain (for example, app)
TypeCNAME
ValueYour Helix hostname (shown with a copy button)

Add the CNAME record at your DNS provider. Helix checks the domain automatically every minute. Once the DNS record resolves and the verification token matches, the domain status changes from Waiting for DNS to Live.

The verification check is passive — no action is needed after adding the DNS record.

Share a preview URL from an active session

When a spec task has an active session, a Share preview URLs section appears on the task detail page. Use it to expose a port inside the agent's container as a public URL.

Click Mint preview URL, enter the port number the agent is serving on, and Helix generates a share-<adjective>-<noun>-<token> URL.

Each preview URL has individual controls:

ControlEffect
OpenOpen the URL in a new tab
CopyCopy the URL to the clipboard
Embed as iframeShows a copyable HTML snippet and a live preview of what the URL renders
RotateRevokes the current URL and mints a new one
RevokePermanently removes the URL

Preview URLs are scoped to the session. They are cleaned up automatically when the session ends.

Operator configuration

By default, Helix expects a reverse proxy (such as Caddy) to handle TLS for custom domains. To enable embedded TLS termination with automatic Let's Encrypt certificates, set:

HELIX_VHOST_TLS_MODE=auto
HELIX_VHOST_LETSENCRYPT_EMAIL=admin@example.com

When auto mode is active, Helix binds :443 for HTTPS and :80 for the ACME HTTP-01 challenge and plain-HTTP redirects. Certificates are issued on demand for the Helix canonical hostname and for any verified domain in the routing table. Certificates are stored in /data/certmagic.

HELIX_VHOST_TLS_MODE=off (the default) leaves TLS to the operator's reverse proxy and is unaffected by this setting.

You also need to expose ports :443 and :80 from the API container so the certmagic listener is reachable. The method depends on your deployment:

Quick setup with install.sh

If you installed Helix with the install.sh script, you can configure embedded TLS at install time — no need to edit .env or apply the compose overlay manually:

# HTTP-01 (standard — Helix directly reachable on :80 and :443)
bash install.sh \
  --vhost-tls-mode auto \
  --letsencrypt-email admin@example.com
 
# DNS-01 — Cloudflare proxy (orange-cloud)
bash install.sh \
  --letsencrypt-email admin@example.com \
  --cloudflare-api-token your-cloudflare-api-token

--cloudflare-api-token implies --vhost-tls-mode auto and sets HELIX_VHOST_ACME_DNS_PROVIDER=cloudflare. Re-running the installer against an existing installation updates .env while preserving any values already set. docker-compose.tls.yaml is downloaded at install time and fetched by --upgrade if absent, so the overlay is available even if you enable TLS after the initial install or upgrade from a release that pre-dates the overlay.

Docker Compose

Helix ships a docker-compose.tls.yaml overlay that adds the :443 and :80 port mappings to the api service. Apply it explicitly when starting Helix:

docker compose -f docker-compose.yaml -f docker-compose.tls.yaml up -d

Host ports default to 443 and 80; override in .env if those ports are in use:

VHOST_HTTPS_PORT=8443
VHOST_HTTP_PORT=8080

Helm chart

Enable the vhostTLS sub-chart values so the Service and Deployment expose the correct ports:

controlplane:
  vhostTLS:
    enabled: true
    httpsPort: 443
    httpEnabled: true   # set false when using DNS-01 (API doesn't bind :80)
    httpPort: 80
  extraEnv:
    - name: HELIX_VHOST_TLS_MODE
      value: "auto"
    - name: HELIX_VHOST_LETSENCRYPT_EMAIL
      value: "ops@example.com"

Cloudflare proxy (DNS-01 challenge)

The default HTTP-01 and TLS-ALPN-01 challenges require Helix to respond directly on :80 and :443. Behind a Cloudflare proxy (orange-cloud DNS), Cloudflare terminates TLS at the edge and intercepts port 80, so these challenges never reach the origin and always fail.

The DNS-01 challenge sidesteps this by writing a TXT record to your DNS provider's API — no inbound port requirements.

Set the following environment variables:

HELIX_VHOST_TLS_MODE=auto
HELIX_VHOST_LETSENCRYPT_EMAIL=admin@example.com
HELIX_VHOST_ACME_DNS_PROVIDER=cloudflare
HELIX_VHOST_CLOUDFLARE_API_TOKEN=your-cloudflare-api-token

The Cloudflare token must be an API token (not a legacy global API key) with Zone:Zone:Read and Zone:DNS:Edit permissions on the zones Helix issues certificates for. Create one in the Cloudflare dashboard under API Tokens.

When DNS-01 is active, Helix skips the :80 challenge listener — it is not used in the DNS-01 flow and is unreachable behind Cloudflare. The :443 HTTPS listener is unchanged.

For Docker Compose, edit docker-compose.tls.yaml to comment out the :80 line before applying the overlay — the API does not bind :80 in DNS-01 mode:

services:
  api:
    ports:
      - ${VHOST_HTTPS_PORT:-443}:443
      # - ${VHOST_HTTP_PORT:-80}:80  # DNS-01: API doesn't bind :80, leave commented

Then apply the overlay:

docker compose -f docker-compose.yaml -f docker-compose.tls.yaml up -d

For Helm, set httpEnabled: false so the Service does not expose :80:

controlplane:
  vhostTLS:
    enabled: true
    httpsPort: 443
    httpEnabled: false   # DNS-01: API doesn't bind :80
    httpPort: 80
  extraEnv:
    - name: HELIX_VHOST_TLS_MODE
      value: "auto"
    - name: HELIX_VHOST_LETSENCRYPT_EMAIL
      value: "ops@example.com"
    - name: HELIX_VHOST_ACME_DNS_PROVIDER
      value: "cloudflare"
    - name: HELIX_VHOST_CLOUDFLARE_API_TOKEN
      value: "your-cloudflare-api-token"