rfc: FilOne Appliance Deployment Strategy - #19
Conversation
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
c9a00e8 to
73fab94
Compare
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
|
|
||
| ### Platform Services | ||
|
|
||
| - A Postgres-compatible database |
There was a problem hiding this comment.
Have you tried running the appliance with yugabyte? I know curio is using yugabyte and maybe we could reach out to them to find out their experiences with the database.
There was a problem hiding this comment.
I know that Curio uses Yugabyte, but I haven't tried it our myself. That's why this RFC says Postgres-compatible 😄
There was a problem hiding this comment.
Yugabyte is a later problem. It's main advantage of stock PostGres is multi-node high availability deployments, which you pay for with additional complexity. This is for a future with super large SPs who want 100% uptime
|
|
||
| Straw-man proposal 2: | ||
|
|
||
| 1. Provider-operated nodes don't use git-based IaaC, they use Docker tags instead. |
There was a problem hiding this comment.
I think this is the better solution as they might be using some other container orchestrator service.
| **Downstream impact** | ||
|
|
||
| - Piri's restart is externally invisible but slow. | ||
| - Ingot drops in-flight requests (no graceful shutdown; SDK retries absorb most, browser pre-signed URLs don't) |
There was a problem hiding this comment.
Given the new routing policy RFC, would ingot nodes be able to re-route the requests during the 'draining' process?
There was a problem hiding this comment.
I don't know, I guess it's something we can investigate later.
| The reconciler does not pull the changes automatically. Instead, we implement a metric & alert to | ||
| let us know when a node is not up to date. |
There was a problem hiding this comment.
Should we get notified for every patch version or only for minor and major version releases?
There was a problem hiding this comment.
What we really need is to get a notification if we haven't applied a security patch in time. I think that means notifying us for both patch & minor versions.
|
|
||
| ### Upgrade Postgres patch/minor version | ||
|
|
||
| The reconciler does not pull the changes automatically. Instead, we implement a metric & alert to |
There was a problem hiding this comment.
Same question as for the OpenBao version alerts; are we going to get alerts for every patch release or just for minor releases?
There was a problem hiding this comment.
What we really need is to get a notification if we haven't applied a security patch in time. I think that means notifying us for both patch & minor versions.
| #### Where did it stop? | ||
|
|
||
| ``` | ||
| journalctl -u reconcile.service -n 200 |
There was a problem hiding this comment.
Are we going to aggregate logs in somewhere?
There was a problem hiding this comment.
For appliances operated by us, we should probably forward all logs to Grafana.
For appliances operated by region providers, it's up to them to decide.
| We expect to upgrade Forge services at most once per week, during a scheduled | ||
| maintenance window. There will be no continuous deployment for every git commit. |
There was a problem hiding this comment.
Based on our discussion in Eng Weekly today, we want continuous delivery to the dev environment.
There was a problem hiding this comment.
Broadly, this is getting close to GTG. (probably drop the raw notes before merge)
I do want to talk about zero downtime. Agree that it's an unrealistic goal for v1.
However, I think we should be clear about our goals and evolution:
-
I think it's important to get to zero downtime as quickly as possible for Ingot. My believe is this simply needs an audit for where Ingot is storing state that isn't fundamentally multiprocess compatible. I believe that is actually very few places. The goal is to clean up any surfaces and then move quickly to blue-green deployments via Caddy as a load balancer.
-
Next comes Piri -- my first thought is we can seperate the web interface of Piri which is near stateless from the task scheduling parts. This may not be as hard as we think as I think the Harmony task scheduler is pretty multiprocess friendly.
-
Postgres/OpenBao are really future work for super large providers.
I just want to capture this knowledge -- if we can keep Ingot up during upgrades, I think we everything else becomes a much later/downstream problem, especially since Ingot has a cache of most of the requested data and can continue serving many requests without Piri.
hannahhoward
left a comment
There was a problem hiding this comment.
My only blocker with the current design is we should clear the raw notes before merge. I'd like @frrist to sign off first though.
|
Thanks for putting this together. The research went deep and much of it I suspect will be valuable to reflect on as we build. But as it stands I think this RFC is doing too much: architecture doc, MVP plan, operating model, post-mvp plan, runbook, and research archive, for several environments at once. For me, that makes it hard to extract the actual decisions being made and why they are being made. Given that, I'd propose a rescope to just a CD dev environment in the rough shape we want. The appliance material (Podman, immutable OS, partner trust boundary, pdp-gated windows, detailed runbooks, etc.) can then land as follow-up RFCs where each can be properly motivated, discussed, agreed upon, and land as a smaller scoped change. A few thoughts on this as it stands in addition to the comments I've left in review:
|
| 5. Upgrades must honours timing constraints, e.g. we cannot upgrade in the window where the Piri | ||
| node is required to submit a PDP proof. |
There was a problem hiding this comment.
| 5. Upgrades must honours timing constraints, e.g. we cannot upgrade in the window where the Piri | |
| node is required to submit a PDP proof. | |
| 5. Upgrades must honour operator obligations on chain. |
| The following services do not support more than one instance running concurrently, therefore upgrades must be implemented as in-place restarts: | ||
|
|
||
| - Ingot | ||
| - Piri | ||
| - Postgres | ||
| - Vault/OpenBao | ||
| - Caddy | ||
|
|
||
| Zero-downtime upgrades are not possible now, this remains an aspirational future goal. |
There was a problem hiding this comment.
nit: I'd drop this into a non-requirements section - and possibly include more non-requirements there, aside from no downtime upgrades.
| 1. Platform Services | ||
| 1. Forge Services | ||
|
|
||
| It is not yet clear who will operate which layer - FilOne or the region operator. |
There was a problem hiding this comment.
I'd argue its fairly clear that providers operate hardware given:
Our selling point to regional providers: you provide hard drives and a VM, we bring our software and
customers.
|
|
||
| This is the bedrock on which we build the rest of the stack. | ||
|
|
||
| 1. The machine: virtual or bare-metal |
There was a problem hiding this comment.
nit: drop virtual - that belongs in the OS layer.
| - A Postgres-compatible database | ||
| - A secure secret manager (OpenBao, unsealed using FilOne's central OpenBao instance) | ||
| - Caddy (TLS termination, cert management) | ||
| - Filecoin RPC API node (Lotus, Forest). Can be initially replaced with an external provider like chain.love. |
There was a problem hiding this comment.
Drop Forest, that implementation is untest/unverified with our stack.
|
|
||
| **TODOs** | ||
|
|
||
| - Rework Piri & Ingot config schemes so that secrets are stored in external files or Vault/OpenBao. |
There was a problem hiding this comment.
far as I am aware, Piri and Ingot don't store sensitive information in config files. All sensitive information is already kept external files.
There was a problem hiding this comment.
You are right that Piri config file does not contain any sensitive information 👍🏻
Based on fil-forge/smelt#11, Ingot config file contains the following secrets:
- Root S3 account key & secret
- postgres_dsn
There was a problem hiding this comment.
Actually, I think Piri will need Postgres connection string in its config file once we switch from SQLite to Postgres for storage.
type PostgresConfig struct {
// URL is the PostgreSQL connection string (only used when Type is "postgres").
// Format: postgres://user:password@host:port/dbname?sslmode=disable
URL url.URL| The deployment process will be implemented as an automated script, but it will be started by a | ||
| developer and use an SSH session on the target machine. |
There was a problem hiding this comment.
For a dev environment I'd expect this to be automated. i.e.: When a service merges to main it gets deployed to dev.
| 5. The workflow performs automated end-to-end smoke tests | ||
| - create a new tenant, create a new access key, create a new bucket, upload/download object, etc. | ||
| 6. If the tests pass, the workflow creates a new pull request to update the per-region infra definition files | ||
| - One pull request per region, so that we can roll out changes incrementally | ||
| - If there is an already open pull request for the same region, the workflow closes it. |
There was a problem hiding this comment.
While not here yet, I'll make the case that this needs a more thorough validation. At a minimum, in addition to a smoke test, I'd include a soak test: https://en.wikipedia.org/wiki/Soak_testing
frrist
left a comment
There was a problem hiding this comment.
I don't want to block this, so approved!
Your call if you want to incorperate any of the comments I left, they're mainly nits. Happy for a doc on a dev environment to land later, if at all.
Only blocking comment here is Hannah's about the notes - which I'd also like cut
|
|
||
| ## Proposal | ||
|
|
||
| 1. Podman + Quadlet for running each infra & app service as a systemd unit. |
There was a problem hiding this comment.
Responding to @frrist's comment:
Podman + Quadlet needs to be motivated or deferred.: The proposal asserts it but never sells it. The alternatives compare against Portainer/Komodo/Kamal but never against the current baseline: plain docker compose. This deserves its own RFC and is a good thing to consider, but I don't believe we need to make a decision on this point yet.
TL;DR: I need to reconsider this decision.
The long answer written by Claude and lightly edited by me:
Arguments for Podman + Quadlet
- Per-service unit granularity fits the mixed-cadence policy. Each service is its own systemd unit, so the reconciler restarts exactly one thing and leaves the rest untouched — directly serving "Postgres and OpenBao must never be auto-touched." Compose reconciles at the project level, and there are documented cases (Container needlessly recreated and then won't start on docker-compose up docker/compose#9357, [BUG]
upIncorrectly recreates containers with no changes docker/compose#10259) of it needlessly recreating containers, including Postgres. - Native on the immutable-OS path. bootc/FCOS ships Podman and does not ship Docker Engine; getting Docker there means rpm-ostree layering, a reboot, and ongoing maintenance against read-only /usr.
- Notify=healthy (Podman ≥5.0) makes a restart genuinely fail if the healthcheck never passes;
OnFailure=gives alerting; ordering handles OpenBao-before-Ingot. Compose's equivalent (up -d --wait) is one project-wide gate, not per-unit. - Rootless, daemonless on semi-trusted partner hardware. No root daemon socket to steal — a well-known privilege-escalation path in Docker's default posture. Fewer long-running moving parts overall.
- Better secrets story. systemd-creds / LoadCredentialEncrypted= gives host-bound, optionally TPM-sealed blobs, versus SOPS+age decrypting into the daemon's environment on the Compose side.
Downsides
- Distro version sensitivity. Ubuntu LTS ships Podman too old for Notify=healthy (24.04 has 4.9.3); Debian 13's 5.4.2 works but predates ReloadCmd= and podman quadlet (5.6.0), needing workarounds. Compose installs trivially anywhere via Docker's apt repo.
- The reconciler needs custom diff/restart logic. Compose's
up -d --waitis itself the reconciler and only recreates changed services; with Quadlet you copy changed units,daemon-reload, and selectively restart yourself —daemon-reloadalone won't restart anything. - Weaker newcomer ergonomics and smaller ecosystem. "One YAML, docker compose up -d" is a friendlier mental model, and most GitOps-lite tooling (Portainer, Komodo) is Compose-only, so those options are foreclosed.
- No native zero-downtime story either way, but Quadlet's is more manual. Compose at least has the docker rollout plugin pattern; Quadlet requires hand-rolled blue/green with templated units.
- Live sharp edges. The Notify=healthy + TimeoutStartSec footgun (podman #27290) must be explicitly configured given Piri's up-to-60-minute drain, and the BoltDB→SQLite transition (#28216) is a known concurrency hazard for Quadlet-at-boot topologies.
Note: I rejected podman auto-update as the CD mechanism because AutoUpdate=registry never fires on digest-pinned images — it would make the registry, not git, the source of truth.
<!-- rfc-preview-link:start --> [📖 Preview](https://github.com/fil-one/RFC/blob/forge-central-deployment-strategy/rfcs/2026-08-forge-central-deployment-strategy.md) <!-- rfc-preview-link:end --> ## Summary Forge consists of two independent sets of services: Forge-Central contains centrally managed set of services operated by the FilOne team, FilOne Appliance contains the software stack powering regional nodes. This pull request proposes deployment strategy for the Central stack. It's a complement for [rfc#19](#19), which describes the deployment strategy for FilOne Appliance. ## Proofs of Concept - Terraform version: fil-forge/infra-central#1 - Pulumi version: fil-forge/infra-central#23
| @@ -0,0 +1,3687 @@ | |||
| # RFC: FilOne Appliance Deployment Strategy | |||
There was a problem hiding this comment.
Minor but FilOne should be written as "Fil One".
| 1. The machine: virtual or bare-metal | ||
| 2. Network connectivity: a public IP under a stable domain name, and open port 443 | ||
| 3. Storage - control plane: a persisted volume mounted as local FS in the machine | ||
| 4. Storage - data plane: an S3-compatible object storage, in the same datacenter, accessible via S3 (HTTPS) |
There was a problem hiding this comment.
I think it's redundant to say accessible via S3 (HTTPS) when we have in the same sentence specified "S3-compatible object storage" which is a HTTP API.
| 4. Storage - data plane: an S3-compatible object storage, in the same datacenter, accessible via S3 (HTTPS) | |
| 4. Storage - data plane: an S3-compatible object storage, in the same datacenter |
|
|
||
| 1. Podman + Quadlet for running each infra & app service as a systemd unit. | ||
| 1. Config files and pinned image versions tracked in git. | ||
| 1. systemd-timer with git-pull script to reconcile. |
There was a problem hiding this comment.
Seems as though we're replicating a systems package manager with auto updates enabled. Why don't we just ship an apt package (host our own apt source) and use unattended-upgrades (https://wiki.debian.org/PeriodicUpdates) (or alternative e.g. cron-apt) to get auto upgrades?
The package gets to administer service starts/restarts on install/upgrade via maintenance scripts and we get a really nice, easy, way to distribute the appliance.
Installing the appliance is a case of adding the aptitude source:
sudo add-apt-repository "deb http://deb.fil-forge.com/forge <codename> main"...and install:
sudo apt install fil-one-appliance...and adding auto updates:
sudo tee /etc/apt/apt.conf.d/51unattended-upgrades-custom << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"FilForge:stable";
};
EOFThere was a problem hiding this comment.
Interesting idea!
How do you propose to deal with the dependencies (Postgres, OpenBao, etc.)?
There was a problem hiding this comment.
They are either dependencies of the package (so get installed automatically by apt when installing the appliance) or they just need to be installed out of band or like sudo apt install postgres openbao fil-one-appliance.
There was a problem hiding this comment.
That said, I'm not sure these have to be local-local, as in on the same machine. As I understand it they need to be available on the local network...
📖 Preview
Summary
FilOne Appliance is a set of services operating FilOne node on infrastructure provided by a regional provider. This RFC proposes how to deploy and operate the appliance - first an MPV to get us off the ground quickly, then a direction towards a more robust & scalable solution.