Setting up a production server
Polari has two production routes. Pick by who is doing it.
| Route | Who | How |
|---|---|---|
| A home computer, for people | Anyone | Install the polari-complete deb, open Isle App Store, choose Create my own isle. The store runs the guided isle install in a window. Nothing else. |
| A server (a small VM or your own swarm) | Developers, or an AI assistant driving a terminal | pol prod guide — one walkthrough with menus, then it deploys. |
This page is the server route. It deploys one of two profiles, both as a docker swarm stack:
- Lean (logins off, the default): the site and documentation, the download page and apt repository, and one Polari backend with the floor set of modules, on SQLite, with no login server. The public distribution point; runs on a $12-class VM. Four services.
- Full (logins on): the lean set plus Keycloak logins, MariaDB, the MinIO file store, the Democratic Political Scorecard, and optionally Odoo. Eleven public names, about 7 GB of declared memory limits. Its credentials are generated at apply time, never typed defaults.
On a fresh VM start with pol prod bootstrap: it installs docker if missing, initialises the swarm, and opens the guide.
From zero, in the browser (no ssh)
You do not need ssh or a terminal program on your own computer. DigitalOcean gives every droplet a terminal in the browser.
- Attach a reserved IP (Networking → Reserved IPs → assign to the droplet). It stays yours across rebuilds, so the DNS you set next never changes. Point your five names at it now if you already know the domain.
- Rebuild the droplet (Droplet → Rebuild → Ubuntu 24.04 LTS). This wipes the disk and keeps the droplet, its addresses and its firewall. Add your SSH key when it asks; you will not need it, but it keeps the root password out of e-mail.
- Open the console (Droplet → Access → Launch Droplet Console). You are root on the fresh machine.
- Paste one line and press Enter:
curl -fsSL https://raw.githubusercontent.com/dausume/polari-suite/main/get-polari.sh | bash
It installs the few packages the CLI needs, then docker, gets the Polari suite into /opt/polari, installs the pol command, initialises the swarm, and opens the guide. Paste the same line again if anything was interrupted; every step picks up where it left off.
- Answer the guide's questions. It shows the droplet's addresses and asks you to confirm the exposure address, checks DNS, asks about the certificate, the profile, the modules, and applies.
Later, from the same console: pol prod status, pol prod cert, pol security vault show.
The guide's screens
pol prod guide opens a full-screen guide (Python, Textual) that resizes with your terminal, including the DigitalOcean browser console. The left column lists the steps and marks where you are: credentials and vault, domain, exposure address, DNS check, certificate, logins and modules, images, installers and notice, review, apply. The right column is the current step's form, or the live log once apply runs. Every fact on screen comes from the machine itself, and every answer is checked before the next step: a step with a problem says what is wrong and does not advance. The pairs that must match are set together and shown together on the review screen, so a registry cannot be chosen without a tag the registry has, a DNS challenge cannot be chosen without DigitalOcean DNS, and Odoo cannot be chosen without the full profile. Apply asks for one more press, writes the answers file, and streams the run. Ctrl+R re-checks DNS after you change records; Ctrl+B goes back; Ctrl+Q quits.
The plain dialogs remain as the fallback when Python's Textual is not installed (pol prod tui-install installs it; POL_PROD_TUI=whiptail forces the old dialogs). Both front ends write the same answers file and run the same steps.
One command
pol prod guide
The guide asks, in order, and remembers every answer in .generated/prod-answers.env:
- Which route — server (this) or home computer (it tells you where to go instead).
- Domain — the public name. It shows where each of the five names resolves right now (
example.org,www.,prf.,api.prf.,apt.) against this host's public address, so you can fix DNS before anything is issued. - HTTPS certificate — two doors:
- Provider-issued, auto-approved (Let's Encrypt): one certificate for all five names, trusted by every browser, renewed weekly. Verified either by an HTTP challenge through this server's port 80 (any registrar, nothing to configure) or by a DNS challenge through the DigitalOcean API.
- Auto-generated: signed by the suite's own certificate authority. Works immediately; browsers warn until that root is imported. You can switch to the provider door later with
pol prod cert.
- User logins — Keycloak handles authentication and user login: accounts, sign-in, and access control per user. It is the default, and brings the scorecard and the file store with it. Without logins the server is open to everyone with no accounts, which suits a plain distribution or demonstration server and is about 1 GB lighter. Add-ons such as Odoo are not first-run questions; they are enabled after the initial deployment (
POL_PROD_ODOO=on pol prod apply), and their subdomain appears only then. - Modules — the floor set (
polariapps, appstore, islemesh, terms); add more at the cost of memory. - Installers — installers are release artifacts: built once, published, fetched here. The guide lists the official releases that carry installers (GitHub Releases of the suite, free for a public project) and stages the chosen one; or build them here, or name another pool (a directory, a release page URL, or
github:<owner/repo>@<tag>), or skip. The Download page lists whatever is staged. - Demonstration notice — whether the apps show the "no personal information" bar and the terms gate.
- Images — a registry prefix to pull the release images from (for example
ghcr.io/dausume/), or empty to build them on this machine from the checkout, and the image tag.
Then it shows the plan and asks once whether to apply.
What apply does
Every step is idempotent, so re-running after a fix is safe.
- Preflight: docker, swarm manager, ports 80 and 443 free, images present, DNS, certificate, staged debs, apt signing key.
- Writes the inputs:
.generated/.env.lean, the frontend and hub runtime configs,nginx.lean.conf. - For the full profile, runs the security setup once: the suite's certificate authority, the Keycloak certificate and admin, random database and file-store credentials.
- Stages the edge certificate: the Let's Encrypt pair when issued, else one signed by the suite CA for every name the profile serves (five for lean, eleven for full).
- Stages the installers per your answer.
- Pulls the release images from the registry, or builds them locally with compose; the hub image carries the site and its documentation, so any swarm node can run it.
- Renders the stack from the profile's compose file (
docker-compose.lean.ymlordocker-compose.prod.yml) and deploys it aspolari-leanorpolari-prod. Configuration and the certificate travel as docker configs and secrets; the debs, apt tree and ACME webroot are directories on the manager, where the proxy and backend are pinned. - If you chose the provider certificate, issues it now (the HTTP challenge needs the proxy up), re-deploys with the new secret, and installs weekly renewal.
- Prints the status board.
Unattended
Every answer is also an environment variable, which is how a script or an AI assistant drives it:
POL_PROD_DOMAIN=example.org POL_PROD_CERT_MODE=letsencrypt POL_PROD_LE_CHALLENGE=http \
POL_PROD_LE_EMAIL=ops@example.org POL_PROD_DEBS=build pol prod apply --yes
pol prod plan prints what apply would do without changing anything. pol prod check is the preflight alone.
One access point, several machines
The proxy runs on the manager and is the only entry point. It reaches every other service by name over the swarm's overlay network, wherever that service was placed, so adding machines does not change the proxy. With locally built images every service stays on the manager (only it has the images); with a registry prefix the services may spread across nodes. An isle is a different world: there the isle agent's own nginx fronts Polari as polari.isle, and nothing here touches it.
Opening and closing an isle to the outside
An isle has its own, smaller version of "production": one door at a time, one person per door, and the isle's names never leave. From a terminal on the isle's core device:
sudo isle url entrypoint enable this device may open doors
sudo isle url expose polari.isle --port 18443 --user alice a door to Polari, basic auth at the door
isle url exposures what is open
sudo isle url unexpose --port 18443 close the door
sudo isle url entrypoint disable back to fully contained
The door refuses to open until the device is designated and its deploy-time credentials pass the security gate. Closing it removes the gateway container; nothing inside changes.
On a DigitalOcean droplet
The droplet tells the server its own addresses. pol prod addresses reads DigitalOcean's metadata service (nothing to configure, no token) and lists the public IPv4, the public IPv6 if enabled, the reserved IP if one is attached, and the private VPC address. The exposure address is the one every DNS A record must carry: the reserved IP when there is one, else the droplet's public IPv4.
Attach a reserved IP before you set DNS. A wiped and rebuilt droplet gets a new public address; a reserved IP stays yours and follows the rebuilt droplet, so the records never change. The guide warns when none is attached and links to the page.
The detected address is a suggestion. The guide asks whether to keep it or to type the address you know is right, for the cases where the machine sees the wrong one or the address is about to change. pol prod addresses --use <ip> records your answer at any time; --auto goes back to detection. The preflight and the status board say which one is in use.
If a DigitalOcean cloud firewall is attached to the droplet it must allow inbound 22, 80 and 443; the host firewall itself is rendered by os-security.
Providers, and where to go
pol prod providers shows which provider fills which role for this deployment, hosting, DNS, certificate, registry and code, with the pages to visit for each. The guide shows the same links at the step where they matter: the DNS page where the A records are set, Let's Encrypt's rate limits and status before a certificate is requested, and the API token page when the DNS challenge is chosen. Provider credentials are never typed into the guide, except the DNS-challenge token, which is read from the environment and never written down.
Domain, subdomains, and what is yours versus external
One thing is external: the primary domain, registered somewhere, with its DNS managed at the registrar, DigitalOcean or Cloudflare. Everything else is a subdomain Polari defines, and a subdomain exists only because a component you enabled needs it: prf and api.prf always, auth, psc, api.psc, files and s3 with logins, odoo with Odoo, apt once installers are handed out, and www only if you ask for it. It is a hostname convention, not a protocol, and most sites just redirect it to the apex. Enable a component later and its name appears; Polari re-renders the proxy and re-issues the certificate to cover exactly the enabled names. Nothing about subdomains is configured at your DNS host.
The internet still needs a DNS record to find each name. That is one A record for the primary domain, and then either one wildcard record, *.yourdomain pointing at the exposure address, which covers every subdomain now and later, or one A record per subdomain. The guide's Names step shows each name with the component that enables it, whether it resolves here, and what external record it needs, and it detects a wildcard.
Credentials and the vault
Nothing the guide generates is left for you to protect by hand. On the full profile the Keycloak admin, the database passwords and the file-store keys are generated once, at random, and recorded in an encrypted, root-only vault at /etc/polari/vault. Read it with sudo pol security vault show; nothing in it is ever printed to a log.
At the start the guide tells you this and asks one rule for provider credentials, the ones you bring yourself, such as a DigitalOcean API token for the DNS challenge: stash all of them in the vault, ask for each one, or none. A stashed token is reused on the next run, so you are not asked twice. The advice stays the same either way: record provider credentials in your own password manager and remove them from the vault afterwards with sudo pol security vault forget 'provider <name>'. pol security status reminds you while any are stashed.
When apply finishes the guide asks what to do with the vault: keep it here, show everything once so you can write it down and then shred it, or export it (with its key, kept apart) to a path you choose and shred the local copy. Unattended runs keep it.
The vault protects against anyone external: a copied disk, a backup, another user, a tarball of the checkout. It does not hide values from the account that runs the deployment, which is deliberate while the setup is being iterated on; an off-machine-key mode exists for later.
One more thing the vault fixed: credential files that still carry placeholder values are no longer reused. Apply moves them aside, generates real ones, and tells you that a database volume created with the old values has to be recreated.
From another machine
pol deploy drives machines listed in the nodes manifest over ssh:
pol deploy tier <node> --check what it qualifies for (member, hardware)
pol deploy tier <node> hardware label it; needs KVM + libvirt on the target
pol deploy install <node> --route swarm-worker join this swarm
pol deploy install <node> --route swarm-server --domain D a standalone production server there
pol deploy install <node> --route isle-member [--host] join this isle (sudo on the target)
pol deploy install <node> --route isle-core [--yes] ship the deb and run the isle install (unattended with --yes)
pol deploy status <node> what runs there, either route
pol deploy uninstall <node> --route … [--yes] the reverse; the isle wipe needs --yes
The app route is meant for people through the Isle App Store, but every step of it is also a terminal command, so a developer or an AI assistant can install, check and remove an isle over ssh.
Add --dry-run to see the exact commands first.
Root on the target is the one thing ssh cannot supply. Two permission groups make it unnecessary after a single setup:
pol deploy grant <node> --group remote ssh + swarm + AI-assisted setup: exactly the commands pol deploy sends, no password
pol deploy grant <node> --group app the app-setup route: what the Isle App Store's doors run for a person
The first grant needs a password once (the command prints the one line to run). The two groups are separate on purpose: a machine set up for people gets app; a machine an operator or an assistant drives remotely gets remote.
Afterwards
pol prod status stack, services, certificate issuer and expiry, DNS, health, terms gate
pol prod cert issue or re-issue the provider certificate
pol prod debs build (re)build and stage the installers
pol prod down remove the stack; the data volume stays
The status board says plainly when the certificate is not yet publicly trusted, and what to run.
What is still yours
- Point DNS at the server before choosing the provider certificate.
- The apt repository is served from
.generated/apt; publishing into it needs the signing key, which lives with the build pipeline's secrets. - The full profile's Keycloak credentials are generated by the security setup on first apply; rotate them with
pol security rotate prodbefore the server faces the internet, and keep the generated env file private. - The old
start-prod.shandprod-setup.shstill exist but only hand over topol prod.
Polari