☸️ Deploy with Helm
A release tag publishes both the image (ghcr.io/cryptos-pki/cryptos-manager:v0.1.0-rc.1, linux/amd64 only) and the chart (oci://ghcr.io/cryptos-pki/charts/fleet-manager, version 0.1.0-rc.1) to GHCR. Install straight from the OCI registry below, or render the chart from a checkout of the cryptos-manager repo for an unreleased commit or a local edit. Kubernetes is one of three ways to run the manager; see Other ways to run it for the other two.
The supported chart is chart/fleet-manager in the cryptos-manager repo. It ships from the same repo and the same release as the manager, so the chart and the config file it renders always match the manager they run. If you are new to the FleetOS Manager, read the FleetOS Manager overview first.
What the chart deploys
The published chart is version 0.1.0-rc.1, app version 0.1.0-rc.1. A checkout's Chart.yaml says 0.1.0; the release workflow sets both to the tag without its leading v.
The chart renders four objects:
| Object | What it is for |
|---|---|
ConfigMap fleet-manager-config | The manager's config.yaml, mounted at /etc/cryptos/fleet/config.yaml. It holds no secrets. |
Deployment fleet-manager | One container named manager, running ghcr.io/cryptos-pki/cryptos-manager by default, or an image you built and pushed yourself. |
Service fleet-manager | Port 443 forwarded to 8443 in the container, and port 80 to 8080 for the redirect to HTTPS. |
PersistentVolumeClaim fleet-manager-node-creds | The node credentials folder. Not created when you name your own claim in nodeCreds.existingClaim. |
The pod runs as user 65532 with a read-only root filesystem, no privilege escalation and every Linux capability dropped. Its one writable mount is the node credentials claim, at /var/lib/cryptos-manager/node-creds.
The probes:
- Readiness calls
/healthz, which the manager answers itself:200when it is serving and its Postgres answers,503when the database does not. A pod whose database is down leaves the Service. - Startup and liveness only check that the HTTPS listener accepts connections. A database outage doesn't restart the pod, and the startup probe allows two minutes, which covers the 90 seconds the manager waits for Postgres before it listens.
What you supply
The chart creates no secrets. Before an install you need these in the release namespace:
-
A Postgres database the cluster can reach, and a Secret holding its connection string, such as
postgres://manager:<password>@db:5432/manager, under the keydatabase-url(or the key you set indatabase.secretKey). Name it indatabase.existingSecret. The chart passes it to the manager as theMANAGER_DATABASE_URLenvironment variable, so the password never appears in the ConfigMap.Instead of Postgres you can set
storage.mode: memory: a single-replica store that keeps nothing across a restart and has no first run or MCP, so it needsoperatorCA.configMap. See the manager's storage guide. -
Storage for the node credentials. The default claim asks the cluster's default storage class for
1Gi,ReadWriteOnce. -
The image tag. A release tag publishes
ghcr.io/cryptos-pki/cryptos-manager, whichimage.repositoryalready points at, but tagged with a leadingv(v0.1.0-rc.1) while the chart's app version has none. Setimage.tag=v0.1.0-rc.1, or the pod can't pull its image. To track an unreleased commit or a local edit, build and push your own and setimage.repositoryandimage.tag; the manager README covers building the image yourself.
The chart refuses to render unless you set database.existingSecret or storage.mode: memory, and refuses both together.
Two more are optional. Leave both out for day zero:
- A TLS Secret with
tls.crtandtls.key, the manager's server certificate, named intls.certSecret. Without it the manager serves a self-signed certificate, kept in Postgres so every pod serves the same one, and logs its fingerprint. Add the Secret once you have a real certificate. - A ConfigMap with
operator-ca.pem, named inoperatorCA.configMap. Without it the operator CA is registered through first run, andfirstRun(autoordisabled) says whether first run may open. With it, that file is the only operator CA source and first run doesn't apply.
Day zero
Install straight from the published chart, with only the database Secret:
helm install fm oci://ghcr.io/cryptos-pki/charts/fleet-manager --version 0.1.0-rc.1 --set image.tag=v0.1.0-rc.1 --set database.existingSecret=fm-postgres
Or, from a checkout, for an unreleased commit or a local edit:
helm install fm manager/chart/fleet-manager --set database.existingSecret=fm-postgres
The install notes print the commands that find the self-signed certificate's fingerprint and the bootstrap token in the log. Then follow Start first run.
Until first run closes, anyone who can run kubectl logs on the manager can start first run. Limit who can read the pod logs in this namespace, including any log shipping, as tightly as access to the manager.
Once you have a real server certificate, set tls.certSecret in an upgrade. The pod rolls and the manager drops the stored self-signed certificate.
When the manager adopts a node, it writes that node's admin key to the claim, and the installed node trusts only that key. If the claim is deleted, the manager can no longer manage any node it adopted. The only way back is a reset from each node's console, which erases the node's key material. helm uninstall leaves the claim in place on purpose (helm.sh/resource-policy: keep). Back it up along with the database, and don't delete it by hand. See Re-adopting a node.
Before you upgrade
Before upgrading to this version, make sure every node is either pinned (server.crt, or the fingerprint recorded at adoption) or has a recorded CA chain. Unpinned nodes are refused after the upgrade. Check node trust before you upgrade lists the nodes that would be refused and how to pin each one.
A helm upgrade that only changes values in the config still restarts the manager: the pod carries a checksum of its ConfigMap, so the new config is read straight away.
After an install or upgrade, the chart's notes print the command that lists how each node is verified, and a warning for every node with insecureSkipNodeVerify.
The values that matter
The full list is in chart/fleet-manager/values.yaml.
| Key | Default | What it does |
|---|---|---|
replicaCount | 1 | Number of manager pods. See the caution below before raising it. |
image.repository | ghcr.io/cryptos-pki/cryptos-manager | The manager image. Pull the published image at a release tag, or build and push your own and point this at it. |
image.tag | "" | Empty means the chart's app version (0.1.0-rc.1), which has no published image: set v0.1.0-rc.1. |
service.port / service.targetPort | 443 / 8443 | The Service port and the container's HTTPS listener. |
service.httpPort / service.httpTargetPort | 80 / 8080 | The redirect-to-HTTPS port and its listener. |
httpRedirectListen | 0.0.0.0:8080 | The redirect listener. Empty turns it off. |
authBypass | false | Development only. Turns off client-certificate login and TLS. |
tls.certSecret | "" | The TLS Secret. Empty serves a self-signed certificate. |
operatorCA.configMap | "" | The ConfigMap with operator-ca.pem. Empty registers the operator CA through first run. |
firstRun | auto | auto or disabled. Applies when operatorCA.configMap is empty; disabled keeps first run shut, so only an operator CA already registered in the database is trusted. |
operatorCRL.urls | [] | http or https URLs of the operator CA's CRLs. |
operatorCRL.configMap / operatorCRL.files | "" / [] | A ConfigMap of CRL files (DER or PEM) and the keys in it to load. The chart mounts it read-only at /etc/cryptos/fleet/operator-crl, and the manager re-reads the files on every refresh. Set both or neither. |
operatorRevocationPolicy | "" | soft (the default when empty) or hard: what the web API does when no fresh revocation data is available. |
operatorOCSP.mode / operatorOCSP.url | "" / "" | off, aia (the default when empty) or url, with url required for mode url and refused otherwise. |
operatorCANode | "" | Removed. A CryptOS node can't be the operator CA; setting it fails the render. See Migrating from operator_ca_node. |
mcp.enabled / mcp.publicURL | false / "" | The MCP endpoint for AI agents. See the manager's MCP guide. |
database.existingSecret | "" | The Secret with the Postgres connection string. |
database.secretKey | database-url | The key inside that Secret. |
storage.mode | "" | memory instead of Postgres: one replica, nothing kept across a restart, no first run or MCP. Can't be combined with database.existingSecret. |
nodeCreds.existingClaim | "" | Use this claim instead of creating one. |
nodeCreds.storageClass | "" | Empty uses the cluster default. |
nodeCreds.accessModes | ["ReadWriteOnce"] | Access modes for the created claim. |
nodeCreds.size | 1Gi | Size of the created claim. |
nodes | [] | Nodes to load into an empty database on first start. |
nodes[].adminCredsSecret | unset | A Secret with the node's admin credentials. See Node admin credentials from a Secret. |
nodes[].insecureSkipNodeVerify | unset | true turns off the check of the node's server certificate. Lab testing only; see Skipping verification in a lab. |
operatorCRL and operatorOCSP apply to the operator CA in operatorCA.configMap, and the chart refuses to render them without it: a CA registered through first run keeps its CRL and OCSP settings on its own record. operatorRevocationPolicy applies to either source. The chart refuses to render any of them with authBypass: true, and refuses a bad value (a policy other than soft or hard, an unknown OCSP mode, a CRL URL that isn't http or https) instead of letting the manager fail at start.
Every pod must hold the admin key of every adopted node, so a ReadWriteOnce claim supports one pod only. The chart refuses to render with replicaCount above 1 unless nodeCreds.accessModes includes ReadWriteMany. With a ReadWriteOnce claim the Deployment uses the Recreate strategy, so an upgrade stops the old pod before it starts the new one and the UI is briefly unavailable.
Node admin credentials from a Secret
A node you list in nodes needs the admin certificate and key the manager presents to it, and what the manager verifies the node against: the node's CA chain, its pinned server certificate, or both. Put them in a Secret, one per node, and name it in the node's adminCredsSecret. The chart mounts the Secret read-only at /etc/cryptos/fleet/node-admin/<name> and points the node's adminCertPath, adminKeyPath and caCertPath at it, so no key material goes in your values or the ConfigMap.
-
Create the Secret in the release namespace, with the keys
admin.crtandadmin.key, plusca.pem(the node's CA chain) once the node has its CA, andserver.crt(its pinned certificate) before that.kubectlworks the same on Linux, macOS and Windows:kubectl create secret generic pki-root-admin --from-file=admin.crt --from-file=admin.key --from-file=ca.pem --from-file=server.crtThe manager reads
ca.pemandserver.crton every connection, so an updated Secret takes effect once Kubernetes refreshes the mount, without a restart. -
Name it in the node's entry:
nodes:- name: pki-rootendpoint: "pki-root.example.org:443"role: rootadminCredsSecret: pki-root-admin
The chart fails the render if a node sets adminCredsSecret together with adminCertPath, adminKeyPath or caCertPath, or has no name. A node without adminCredsSecret keeps the paths you give it.
With Postgres, the manager copies nodes into the database on its first start only. A node you add to the list later does not appear. See Listed in the config file.
This is for nodes you list yourself. Nodes the manager adopts keep the admin key it makes for them on the node credentials claim.
Render the chart and check it
You can render the chart without a cluster and read what it would create. git and helm work the same on Linux, macOS and Windows, so these commands are the same everywhere.
-
Clone the cryptos-manager repo:
git clone https://github.com/CryptOS-PKI/cryptos-manager.git manager -
Lint the chart:
helm lint manager/chart/fleet-managerExpected outputThe chart passes. Lint renders it with the default values, so it notes that no store is set (you choose a database Secret or
storage.mode: memoryat install) and that it has no icon.level=INFO msg="funcMap fail" message="either database.existingSecret or storage.mode: memory must be set: the chart no longer falls back to an undeclared in-memory store (see docs/storage.md)"==> Linting manager/chart/fleet-manager[INFO] Chart.yaml: icon is recommended1 chart(s) linted, 0 chart(s) failed -
Render the Deployment with the names filled in:
helm template fm manager/chart/fleet-manager --set tls.certSecret=fm-tls --set operatorCA.configMap=fm-operator-ca --set database.existingSecret=fm-postgres --show-only templates/deployment.yamlExpected outputThe container gets the connection string from your Secret, not from the ConfigMap:
env:- name: MANAGER_DATABASE_URLvalueFrom:secretKeyRef:name: "fm-postgres"key: "database-url"A missing value stops the renderLeave out both
database.existingSecretandstorage.modeand the render fails instead of deploying a manager with no declared store:Error: execution error at (fleet-manager/templates/deployment.yaml:17:4): either database.existingSecret or storage.mode: memory must be set: the chart no longer falls back to an undeclared in-memory store (see docs/storage.md)
Other ways to run it
FleetOS Manager deploys three ways:
- Kubernetes, above -- the published chart, or a checkout for an unreleased commit or a local edit.
- A container (Docker or a Docker-compatible runtime) on a bare-metal host, with PostgreSQL alongside it on the same host for easy upgrades: Single host with
docker compose. The compose file starts for day zero with onlyconfig.yamland the Postgres password, plusMANAGER_VERSION=v0.1.0-rc.1(the compose file defaults tolatest, which an rc never publishes): itstls,operator-caandoperator-crlread-only mounts are commented out until you uncomment each one with its key inconfig.yaml. PostgreSQL is always yours to provide -- the compose file'spostgresservice is a worked example you run and own, not something the manager ships or manages. - A plain Linux host with systemd, no container runtime, and a Postgres you provide: Deploying FleetOS Manager standalone.
Every deployment needs an operator certificate before anyone can log in. The manager's Operator credentials guide shows how to mint one.
Where to go next
- FleetOS Manager overview: what the FleetOS Manager holds and how nodes join it.
- The web UI: logging in and finding your way around.