Skip to content

Deployment overview

PicoAide Harness is delivered for the enterprise intranet: a single machine runs the server, employees install the client, and the data and keys all stay on the enterprise’s own machines. This page explains the deployment forms and the deliverables; see the other pages in this section for the actual steps.

The repository’s docs/deploy/AI-DEPLOY.md is the only authoritative deployment guide (first deployment / upgrade / rollback / troubleshooting + the four data-safety rules), and can be handed straight to an AI agent to execute. This Wiki is the same process written for humans; if the two ever diverge, the repository document and the code win.

FormSuitable forDescriptionEntry point
Standalone desktopIndividuals / small teamsInstall only the desktop client. The client ships a local Harness runtime and starts the service on the local machine; sessions and credentials stay on that machine, and no server is neededDesktop client
Containerized on an enterprise intranet (recommended)Everyone in the organizationAn intranet server runs the three containers caddy + server + postgres; accounts, gateway, quotas, billing and approvals are centralized on the serverContainer deployment
Merged into an existing reverse proxy / single binaryData centers that already have a unified entry pointWhen 80/443 are already taken by a shared Caddy/nginx, start only server + postgres and merge them into the existing vhost; a single binary + external PostgreSQL is also supported (including migrating from systemd)Operations & troubleshooting

The server has exactly one release artifact: a container image. Everything needed for deployment is already inside the image — no repository clone, no fetching configuration from the internet, and no install scripts of any kind.

Deliverable = one container image
├─ server binary (with the embedded webadmin Admin Console)
├─ client installers for three platforms + CLIENT-RELEASE.json ← employees download from here
├─ docker-compose.yml + Caddyfile.{internal,autocert,manual} + .env.example
├─ VERSION / CHANNEL ← this deployment's version and channel
└─ channel/ ← branding and copy (channel content)

A single command exports the deployment files to the deployment directory (via the image’s built-in PICOAI_UNPACK_STACK entry point):

Terminal window
mkdir -p /opt/picoaide
docker run --rm -v /opt/picoaide:/out -e PICOAI_UNPACK_STACK=/out \
picoaide-harness-server:<version>
ls -1 /opt/picoaide # docker-compose.yml / Caddyfile.* / .env.example / VERSION / client/

Exporting uses replace semantics: docker-compose.yml, Caddyfile.*, .env.example, client/ and VERSION are cleared before being written; .env, picoaide-data/, pg-data/, caddy-data/ and certs/ are never touched.

No image registry is involved (GHCR has been retired). Images for every channel come from the update server, with one separate directory per channel:

https://release.picoaide.com/<channel>/latest.json ← version manifest (the server's upgrade check reads it too)
https://release.picoaide.com/<channel>/releases/<version>/picoaide-server-<version>-amd64.zip
https://release.picoaide.com/<channel>/releases/<version>/SHA256SUMS ← always verify after downloading

Key fields of latest.json:

FieldMeaning
channel_idWhich channel this manifest belongs to (the server enforces the comparison, see Channels & white-label)
server.versionTarget version (without v, e.g. <version>)
server.image_tagImage tag (with v, e.g. v<version>); after import both the <version> and v<version> tags exist, plus a channel-scoped <channel-id>-<version> tag (required when two channel stacks share a host — see Upgrade, backup and rollback)
server.image_assetDownload URL of the image archive
client.versionClient version released with this image version (same source as the server)
  • The update server keeps only the 3 most recent versions; earlier versions come from the GitHub Release (the complete historical archive for public channels);
  • The GitHub Release only publishes the image archives and SHA256SUMS of public channels (official / pre-release); brand channels are custom customer deliveries and do not go through the public Release;
  • For environments without internet access, see Air-gapped deployment.
ItemRequirement
ServerLinux x64; Docker ≥ 24 and Compose v2 (docker compose, not docker-compose), openssl, curl, unzip
Resources≥ 4 cores / 8 GB RAM / 50 GB free disk recommended (pg-data/ keeps growing)
NetworkThe server must be able to reach https://release.picoaide.com (update checks + image downloads); employee computers need no internet access at all
PortsCaddy uses host ports 80/443 (changeable via CADDY_HTTP_PORT / CADDY_HTTPS_PORT)
ClientWindows 10+ x64 / macOS 12+ (Apple silicon) / Linux x64; no Node.js, pnpm or DSH required
Employee clients / browsers
│ HTTPS(80/443)
▼
Caddy 2 (reverse proxy + TLS termination, fixed IP 172.28.0.2)
│ HTTP:8080 (compose private subnet only)
▼
Go server (non-root uid 10001, fixed IP 172.28.0.3)
│
▼
PostgreSQL 18 (built-in container, fixed IP 172.28.0.4, data ./pg-data)
  • A custom bridge private subnet (default 172.28.0.0/24, configurable via NETWORK_SUBNET); container IPs are declared fixed in compose and stay unchanged after rebuilds/upgrades;
  • The server does not map a host port, so external traffic can only enter through Caddy (intranet isolation + reduced attack surface);
  • All persistent data uses ./ bind mounts, never named volumes:
DirectoryContentNotes
picoaide-data/Application data + master.keyLosing it means the upstream keys encrypted in the database are permanently undecryptable; a backup is mandatory
pg-data/Built-in PostgreSQL 18 dataMounted at /var/lib/postgresql in the container (since PG18 the data lands in the 18/docker/ subdirectory)
caddy-data/ caddy-config/Caddy certificate store and configurationMust be backed up in auto mode, otherwise certificates are re-issued
certs/Manual certificatesmanual mode only
deploy-backup/Backup outputWritten by the backup steps

App access model (since 2026-09-19: apps need no public entry point)

Section titled “App access model (since 2026-09-19: apps need no public entry point)”

Employee-built WASM apps open only inside the desktop client: the client opens a dedicated app window that loads the custom-scheme address <channel app-origin scheme>://<app_id>/ (the scheme comes from the channel configuration; it is picoaide-app for the official channel), and the client forwards it to the server’s single entry point POST /api/client/v2/apps/wasm/:app_id/request (carrying the employee token).

  • The server needs no public access surface for apps: no app-specific DNS record, no app-specific certificate, and no extra site block in Caddy — the three certificate modes below serve the main site domain only;
  • The pre-2026-09-19 “dedicated app domain + browser access” chain has been removed entirely, so browsers can no longer reach apps;
  • Upgrades must keep server and client on the same version: old clients can no longer open apps, so employees’ clients must be upgraded to the matching version as well (the client ships inside the server image, see Client delivery & updates).

TLS_MODE in .env decides which Caddyfile template is mounted:

ModeTemplateSuitable forPrerequisite
internalCaddyfile.internalPure intranet / no public domain (most common)None; on first connection the client must trust Caddy’s local CA
autoCaddyfile.autocertA public domain that connects directly to this machineThe domain’s A record points to the machine’s public IP and 80/443 are open to the internet; going through a CDN will fail, and IPs are not accepted
manualCaddyfile.manualThe enterprise already has a proper certificate (IPs supported)Provide certs/server.crt + certs/server.key

How to decide: the domain resolves to a public address and can be reached directly → auto; otherwise → internal. Deployments by IP always use internal or manual.

Four iron rules (violating them causes unrecoverable data loss)

Section titled “Four iron rules (violating them causes unrecoverable data loss)”
#ForbiddenReason
1Never run docker compose down -v, docker volume prune or docker system prune --volumes-v / prune delete data volumes and image layers, taking the database and master.key with them; the data lives in bind-mounted directories, so down (without -v) does not delete it
2Never use the latest tagIt is not reproducible and gives no rollback anchor; always use a concrete version such as vX.Y.Z
3Always back up before an upgrade, and confirm the backup files are not emptypicoaide-data (including master.key) + pg_dump; if master.key is lost, every encrypted upstream key in the database can never be decrypted again
4Never overwrite an existing deployment directory with .envA .env already present in the deployment directory means it has been deployed before — that is an upgrade scenario, so follow the upgrade process instead of reinstalling

Also: do not delete the old image before the health check passes (it is the rollback anchor); do not change the fixed IPs / subnet in compose just to get the service running; database migrations are irreversible, so rolling back the image cannot downgrade the database to the old schema.

  1. Container deployment — the complete first deployment, from fetching the image to the health check
  2. Upgrade, backup & rollback — version check, backup, switching and rolling back
  3. Client delivery & updates — clients ship with the server; employees need no internet access
  4. Channels & white-label — official / pre-release / enterprise-custom channels
  5. Air-gapped deployment — getting the package when the server has no internet access
  6. Operations & troubleshooting — reverse proxy, certificates, backup and restore, common failures