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 mainThe deployment sequence is:
- Provision a fresh persistent sandbox.
- Clone the repository and check out the pushed commit.
- Execute
.helix/startup.shwithHELIX_WEB_SERVICE_PORTset. - Poll the configured port until the application responds (up to 90 seconds).
- 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:
| Field | Value |
|---|---|
| Name | Your subdomain (for example, app) |
| Type | CNAME |
| Value | Your 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:
| Control | Effect |
|---|---|
| Open | Open the URL in a new tab |
| Copy | Copy the URL to the clipboard |
| Embed as iframe | Shows a copyable HTML snippet and a live preview of what the URL renders |
| Rotate | Revokes the current URL and mints a new one |
| Revoke | Permanently 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 -dHost ports default to 443 and 80; override in .env if those ports are in use:
VHOST_HTTPS_PORT=8443
VHOST_HTTP_PORT=8080Helm 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-tokenThe 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 commentedThen apply the overlay:
docker compose -f docker-compose.yaml -f docker-compose.tls.yaml up -dFor 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"