Installing on a private network#
For a VPN, a LAN or an air-gapped site: the workspace must work although nothing can reach the internet. This page says what the stack
asks of the internet, what the private profile changes, and what is not solved yet. It was written from reading the code and from
the pieces tried one by one. Tried on 2026-10-22 on a throwaway workspace (provisioned with --profile private, then removed): the profile values reach .env.<slug>, the five images build from the working tree and the seven containers are healthy (workspaces.sh status); the built dashboard has no reference to Google Fonts, unsplash or picsum and carries Inter; the editor keeps only the code paths for fonts a theme names and for the picsum default; a woff2 uploaded to the DAM is checked, registered, listed, and copied by the site generator's font step (the copy is byte-identical). A stack with no route out has not been run, so the last section is the list of what to check.
What the stack asks of the internet#
| When | What | What to do |
|---|---|---|
| Starting a workspace | nothing: no call to the internet was found in core-api, gateway or asset-processor at start | |
| Running | only what an operator configures: AI providers, mail providers, OneSignal, Cloudflare Images, Figma. All are off until configured | leave them unconfigured, or point them at services on your network (an AI model, a mail relay) |
| In the dashboard and the editor | Inter and the icons ship with the apps. Fonts a theme names, and the build-time list VITE_FONT_FAMILIES, come from Google Fonts |
the profile empties VITE_FONT_FAMILIES; upload the fonts you need in Settings > Custom fonts (see settings guide) |
| Placeholder pictures | generated pages and the editor's starter image used a photo from picsum.photos |
the profile sets PLACEHOLDER_IMAGES=local and VITE_PLACEHOLDER_IMAGES=local: a flat grey picture carried by the code |
| Image fallback in the DAM | a stock photo from unsplash.com (changed for everybody: it is now a grey picture carried by the app) |
|
| DAM preview of Word/Excel/PowerPoint files | opens the file in Microsoft's online viewer (view.officeapps.live.com), which also sends the file's address to Microsoft |
not available without internet; do not use it where that matters |
| A page that embeds a YouTube video | the visitor's browser loads the YouTube player | the author's choice; do not embed on a private site |
| Installing and building | the images are built from source (pnpm install reads the npm registry; the Dockerfile runs apt-get and apk), and the compose files pull postgres, valkey and nginx by name |
see "Not solved yet" |
| TLS | Let's Encrypt needs the internet | use a certificate of your own CA (below) |
The private profile#
./scripts/provision-workspace.sh <slug> <hostname> <index> --scheme https --profile private
It appends deploy/profiles/private.env to the workspace's env file: VITE_FONT_FAMILIES= (empty), PLACEHOLDER_IMAGES=local,
VITE_PLACEHOLDER_IMAGES=local and OUTBOUND_ALLOW_PRIVATE=true. The last one is the default; it is written down so that the choice is
visible. On a private network a model, a mail relay or a data source on a private address is the normal case; set it to false and list
the hosts that remain reachable in OUTBOUND_ALLOW_HOSTS if authors are not fully trusted (configuration reference).
Everything the profile sets can be changed with --set KEY=VALUE. The VITE_ values are read when the images are built.
TLS from your own certificate authority#
enable-tls.sh asks Let's Encrypt, which needs a public host name. For a certificate of your own CA, put the files where the HTTPS vhost
reads them and render the vhosts without asking anyone:
mkdir -p deploy/certbot/conf/live/<hostname>
cp fullchain.pem privkey.pem deploy/certbot/conf/live/<hostname>/ # the certificate with its chain, and the key
chmod 600 deploy/certbot/conf/live/<hostname>/privkey.pem
./scripts/enable-tls.sh <slug> --reuse-cert
--reuse-cert no longer checks that the host name looks public (that check is about Let's Encrypt reaching the host). Renewal is yours:
replace the two files and reload the proxy. The browsers of your users must trust the CA.
Not solved yet#
- Installing without the internet. Released images can be used instead of a build (2026-10-22):
provision-workspace.sh <slug> <host> <index> --profile private --image-prefix registry.internal/whoobe --image-version 0.3.0starts the five application images as<prefix>-core-api:0.3.0,-asset-processor,-gateway,-admin-dashboardand-whoobe-one-spike, pulled when missing (or already loaded withdocker load), and builds nothing.workspaces.sh upgrade v0.3.1moves such a workspace to the next version the same way (tried on 2026-10-22 in a throwaway clone: backup, tag checkout,WHOOBE_VERSIONmoved, containers recreated on the new images without a build; on a failure the env keeps the running version). The dashboard and the editor take their addresses, fonts and placeholder mode from the environment at start, so one image serves every workspace. What a private host still needs: the images andpostgres,valkeyandnginxmoved in withdocker save/docker loador a registry mirror, and the Garage image (docker build ./docker/garageon a host with access, then save/load; the script builds it when it is missing, which needs thedxflrs/garageandalpinebase images). Tried on 2026-10-23 againstghcr.io: a trial tag ran the release workflow, which published and signed all six images; a workspace was installed on an isolated network from--image-prefix ghcr.io/swina/whoobe --image-version 0.3.0-rc.1(nothing built, 7/7 healthy,workspaces.sh verify0). The trial packages were deleted afterwards; the first real release publishes them again. - The run itself (2026-10-22): a stack on a Docker network created with
internal: true(scripts/security/offline.override.yml:SEC_COMPOSE_FILE=... stack.sh upfor the security stack,--set COMPOSE_FILE=docker-compose.yml:<override>for a workspace). The containers had no default route, public names did not resolve (EAI_AGAIN) and1.1.1.1was unreachable. Docker does not publish ports of such a network, soscripts/security/internal-bridge.shruns one forwarding container on that network and on the default bridge; the stack's own containers stay isolated. Results:workspaces.sh verifyexits 0 (7/7 healthy, health ok); the API suite on the isolated stack: 2335 tests, 44 failed in the first run, and none of the failures was a call to the internet: 2 were real bugs of the nginx template (below), 6 an old test env file withoutMAIL_SETTINGS_ENCRYPTION_KEY(passed after adding it), and 36 (cdn-mirror,cdn-origin,oidc-login) need the stack to call a mock server on the host, which an isolated network forbids by design; those 49 tests pass on the same stack on a normal network. The dashboard and the editor were then opened in Chromium (Playwright) on a workspace of this kind, through the shared nginx proxy on port 80:/config.jscarried the workspace's addresses, login and the dashboard routes worked, the editor drew its starter image as the local grey picture, and every request of the whole session went tolocalhost(no external host, no failed request, no page error). An uploaded woff2 registered throughPOST /fontsappeared as an@font-facein the editor, was fetched from/dam-asset/...and loaded by the browser. A site generated withwhoobe-publishfrom that kind of workspace copied the uploaded font intopublic/fonts/with a local@font-faceand no Google reference, and a rendered email carries an@font-facewith the file's public address (use--asset-base-urlwhen the generator reaches the API on its own port: the DAM files are served by the proxy). Not done: building the generated site (needs an npm registry or mirror), a real send, real AI, TLS. Seen on the way, not specific to a private network, and fixed (2026-10-22): behind the workspace nginx a direct load or refresh of/settings,/pagesor/documentsanswered from core-api instead of the dashboard; the template now sends browser navigations to those three paths to the dashboard (CHANGES.md; an existing workspace needsscripts/render-workspace-nginx.sh <slug>and an nginx reload).
What to check on a first installation#
docker compose -p ws-<slug> ps: every container healthy;./scripts/workspaces.sh verify <slug>exits 0.- Open the dashboard and the editor with the browser's network panel: no request to a host outside your network (the Office viewer and YouTube only when someone uses them).
- Create a page from the wizard: pictures are the grey placeholder, not broken images.
- Upload a
.woff2in Settings > Custom fonts, use it in a page, publish: the generated site has the file underpublic/fonts/. - Restore a backup on a spare machine (backup, restore and DR).
Last updated