Web Deployment
The portal is a local-first FastAPI service. It can run directly on a workstation, behind a Jupyter/codeserver proxy, behind nginx/Caddy, or as a systemd service.
Local run
scripts/run_portal.sh
Defaults:
host: 127.0.0.1
port: 8768
runtime: runtime/portal
assistant: disabled
Direct CLI:
.venv/bin/fable-pyculator web --root . --host 127.0.0.1 --port 8768
Authenticated remote access
A non-loopback bind requires explicit opt-in. Add built-in HTTP Basic auth or put the portal behind an authenticating reverse proxy. Built-in auth is configured with two values:
FABLE_PORTAL_HOST=0.0.0.0 \
FABLE_PORTAL_ALLOW_REMOTE=1 \
FABLE_PORTAL_AUTH_USER=operator \
FABLE_PORTAL_AUTH_PASSWORD='a-long-random-password' \
scripts/run_portal.sh
The unauthenticated exception is GET /api/health so a monitor can check liveness. Every API and
static asset otherwise requires Basic auth when configured.
Jupyter / codeserver proxy
If the portal is published under a path such as
/user/me/proxy/8768, pass the same prefix as --root-path:
FABLE_PORTAL_ROOT_PATH=/user/me/proxy/8768 \
FABLE_PORTAL_HOST=0.0.0.0 \
FABLE_PORTAL_ALLOW_REMOTE=1 \
scripts/run_portal.sh
The SPA uses relative API/static URLs, so it works at / and under a prefix. Make sure the proxy
URL ends with a trailing slash; a bare path can resolve relative assets incorrectly. The example
nginx config redirects /fable to /fable/.
Systemd
Example files:
deploy/fable-pyculator-portal.service
deploy/portal.env.example
deploy/nginx-fable-portal.conf.example
Install outline:
sudo install -d -o fable-portal -g fable-portal /opt/fable-pyculator /var/lib/fable-pyculator
# install/extract the package and venv under /opt/fable-pyculator
sudo install -m 600 deploy/portal.env.example /etc/fable-pyculator/portal.env
sudo install -m 644 deploy/fable-pyculator-portal.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now fable-pyculator-portal
Edit the environment file before starting. In particular, set a real password, keep
FABLE_PORTAL_ALLOW_AGENT=0 unless the local assistant is intended, and point
FABLE_PORTAL_ROOT/FABLE_PORTAL_RUNTIME at persistent paths.
Reverse proxy
The example nginx config terminates TLS and proxies to loopback. If the portal is mounted under
/fable/, use FABLE_PORTAL_ROOT_PATH=/fable. Keep FABLE_PORTAL_HOST=127.0.0.1 when nginx
is on the same host. Set a long proxy_read_timeout because generated-model runs and prepared
specs can take minutes.
Cloudflare Access
The public deployment may sit behind Cloudflare Access, as fable-pyculator.01101.dev does. In
that case every path, including /api/health, redirects to the Access login unless a service token
or an Access bypass policy is used.
Use a service token for verification:
export CF_ACCESS_CLIENT_ID='...'
export CF_ACCESS_CLIENT_SECRET='...'
python scripts/check_deployed_portal.py \
--url https://fable-pyculator.01101.dev \
--expect-version 0.1.0a6
The checker prints the deployed version, prepared models, assistant state, and artifact checksum states. It never writes credentials to disk.
If the portal also has built-in HTTP Basic auth, set FABLE_PORTAL_AUTH_USER and
FABLE_PORTAL_AUTH_PASSWORD. Both Access and Basic auth must accept the request.
A health-check bypass is an alternative for monitoring:
create an Access policy that allows
GET /api/healthwithout login;leave every other path protected;
accept that the version and prepared-model metadata in the health response become public;
verify the bypass with a plain
curlfrom the monitoring host.
Do not bypass the whole site merely to make health checks work.
Live upgrade runbook
Run
scripts/check_deployed_portal.pyand record the current version and artifact states.Install the published release into the service environment:
python -m pip install --pre 'fable-pyculator[web]==0.1.0a6'.Restart the service or systemd unit.
Re-run the checker with
--expect-version 0.1.0a6.Log in through Access and run one prepared-model scenario.
Record the deployment URL, version, commit/tag, workflow runs, and any limitations.
Security boundaries
The portal has no user accounts or per-user isolation.
Basic auth is a single shared credential; use a reverse proxy for stronger controls.
The assistant is disabled by default. With
FABLE_PORTAL_ALLOW_AGENT=1it runs the localopencodeCLI with shell and repository access.Source workbooks, generated models, saved runs, chats, and caches are local files. Keep the runtime directory on a controlled filesystem.
Do not expose the service directly to the public internet without TLS and authentication.
Public deployment handoff
A public deployment is a maintainer-controlled action. Before provisioning anything, collect these inputs and record them in the deployment report:
public hostname and DNS owner;
TLS approach and certificate/ACME owner;
reverse-proxy path, including whether the portal is mounted at
/or a prefix such as/fable;credential source for
FABLE_PORTAL_AUTH_USERandFABLE_PORTAL_AUTH_PASSWORD;service user, install root, runtime root, and backup location;
whether the local assistant is enabled, and who owns the OpenCode process and its access;
monitoring target for
GET /api/healthand the expected public/private boundary;firewall, proxy timeouts, and maximum upload/response settings;
the named maintainer who will approve the deployment and receive the rollback instructions.
After those inputs exist, the deployment sequence is:
Build and pass the release-candidate checks.
Install the wheel and runtime under the agreed service paths.
Configure the environment file with a real password, then start the systemd unit or proxy wrapper.
Verify unauthenticated
GET /api/healthsucceeds and every other route returns401.Verify the SPA, static assets, and one prepared model run through the public prefix.
Record the URL, commit, package version, workflow runs, and any limits in the phase report.
Keep the previous package artifact and runtime backup available for rollback.
No password, token, private workbook, or personal path belongs in a tracked file.