Skip to content

feat(cli): self-host runtime options for containers - #147

Merged
Madraka merged 1 commit into
mainfrom
feat/selfhost-runtime
Nov 7, 2026
Merged

Madraka merged 1 commit into
mainfrom
feat/selfhost-runtime

Conversation

@Madraka

@Madraka Madraka commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

What

Step 1 of #143 (self-host Docker image): the runtime options a container cannot do without.

  • cloudhal studio --host <addr> / CLOUDHAL_HOST: the address to listen on (serveNode keeps 127.0.0.1 by default; 0.0.0.0 in a container). --host alone still means every interface, for dev too (--host [<addr>]; parseArgs has no optional values, so a bare flag is filled in before parsing).
  • --base-url <url> / CLOUDHAL_BASE_URL: the public origin behind a reverse proxy. It becomes Better Auth's baseURL (session cookies are __Secure-/Secure for https), a trusted origin for the API's own CSRF check, and the URL the command prints. Origins only; a path is refused.
  • --behind-proxy / CLOUDHAL_BEHIND_PROXY=1: serveNode's new trustProxy rewrites each request to the scheme and host in X-Forwarded-Proto / X-Forwarded-Host (first value of a chain, validated), so everything derived from c.req.url (CSRF and MCP origin checks, upload tickets, links) sees the public origin. Without the flag the headers are ignored, as before. forwardedUrl / forwardedRequest are exported from @cloudhal/api/node.
  • --init-if-empty / CLOUDHAL_INIT=<starter> (with --starter, --name / CLOUDHAL_SITE_NAME): when the folder has no cloudhal.json, studio runs init first (reused, not duplicated), so a container starts on an empty /data volume. A folder with other files is refused, as init refuses it.
  • studio() returns the bound port next to url. New module packages/cli/src/commands/studio-options.ts holds the resolution (flags over environment) so studio() stays within the function limit.

Tests: packages/cli/test/studio-selfhost.test.ts (option resolution, bare --host, first-run scaffold once, a proxied sign-up that gets a Secure cookie and passes the CSRF check while a foreign origin is refused) and packages/api/test/node-proxy.test.ts (forwardedUrl, serveNode with and without trustProxy, body passthrough).

Checklist

  • pnpm lint, pnpm typecheck (api, cli), pnpm size:check and the touched test files pass locally (the full pnpm check runs in CI)
  • Contract change? pnpm gen was run and generated files are committed (docs/adr/0001) — n/a
  • User-visible change in a published package? pnpm changeset added
  • Commits are signed off (git commit -s, DCO)
  • Docs / AGENTS.md updated if behaviour or commands changed (cloudhal --help, which the docs site's CLI reference is generated from)

🤖 Generated with Claude Code

`cloudhal studio --host <addr>` (CLOUDHAL_HOST) picks the address it
listens on, `--base-url <url>` (CLOUDHAL_BASE_URL) the public origin for
Better Auth's baseURL, the API's trusted origins and the printed URL, and
`--behind-proxy` (CLOUDHAL_BEHIND_PROXY=1) makes `serveNode` take each
request's scheme and host from X-Forwarded-Proto/Host (new `trustProxy`,
`forwardedUrl`, `forwardedRequest` in @cloudhal/api/node). `--init-if-empty`
(CLOUDHAL_INIT=<starter>) scaffolds the project through `init` when the
folder has no cloudhal.json, so a container starts on an empty volume.
`--host` now takes an address for `dev` too; alone it still means every
interface.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
@Madraka Madraka mentioned this pull request Nov 7, 2026
3 of 5 tasks
@Madraka
Madraka merged commit 8e896d1 into main Nov 7, 2026
4 checks passed
@Madraka
Madraka deleted the feat/selfhost-runtime branch November 7, 2026 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant