Platform onboarding (what a GitOps platform repository must add)¶
Internals, for contributors and for operators wiring obsync into a GitOps platform.
Dated 2026-09-07. This page is for a deployer who runs obsync the way this project is delivered: a signed image and OCI chart from this repository's publisher, a change in a platform repository selecting an exact digest, and a reconciler applying a HelmRelease from a signature-verified OCIRepository. Nothing here is done from this repository, and nothing here describes any particular installation: every name, address, size and policy below is the deployer's own choice (requirement 11).
Posture. The shape this chart is written for is a single-node cluster
reached over private connectivity, LAN or VPN, with no public hostname, no
public access application and no public route, which is OBSYNC_EDGE=none.
Items 1 to 3 describe the published-hostname path for a deployer who wants
one, and they are optional. Behind your own reverse proxy the server stays at
OBSYNC_EDGE=none and trusts forwarded addresses only from
OBSYNC_TRUSTED_PROXY_CIDRS. Only when the edge is Cloudflare's does the
deployment move to OBSYNC_EDGE=cloudflare, and in that mode the server
refuses any request without the edge's connecting-address and request-id
headers.
Whatever fronts the Service, privately or publicly, routes to
http://obsync.<namespace>.svc.cluster.local:8080 (the chart's Service is
named obsync, in whatever namespace the release installs into), and the pod
label an egress policy must select is app.kubernetes.io/name: obsync.
Neither is the namespace name: an egress rule selecting the namespace matches
no pod this chart renders.
- Tunnel (published-hostname path, optional): one per-application tunnel with one hostname rule and a terminal 404 rule, its own credential, DNS record, connector workload and network policy. A platform that caps how many tunnels it admits has to admit one more first.
- Hostname (published-hostname path, optional): one proxied record for
one hostname (
sync.example.orgstanding in for the deployer's own); no new zone, and on the free tier of a provider that offers one, no spend. - Access policy (optional): one application on that hostname with an
identity policy for the dashboard paths and a service-token policy for
/v1/*. The plugin sends its custom request headers when configured; the pairing code can carry them. - Namespace and reconciler: a namespace of the deployer's choosing,
prerequisites, default-deny, an OCIRepository with the chart release's
annotation, an exact
ref.digest, theoci://chart URL and the publisher'smatchOIDCIdentity; a HelmRelease with the platform's own history, drift-detection and rollback settings.deploymentReadystaysfalseuntil items 5 and 6 exist on the cluster: false renders every object with zero application replicas, so the claims can bind their volumes first; true, set by one reviewed values change afterwards, scales the Deployment to its one replica. - Storage: two static local PersistentVolumes on a local class, one host
directory per role (
docs/kubernetes.mdbuilds exactly this), sized to the node's disk with the journal claim at or above the watermark floor (docs/storage.md), node affinity to the node that holds the disk,Retain; pre-bound to the claims the chart creates, which areobsync-blobsandobsync-journalin the release's namespace. Every object this chart renders is named for the application,obsync, and never for the namespace, so a volume pre-bound to a claim named after the namespace binds to nothing and the pod waits forever. Growing a volume later is a PV capacity edit and a claim resize. Directory creation on the host follows whatever procedure the platform already uses for host paths. - Secret:
OBSYNC_SERVER_KEYas an encrypted, reconciler-managed Secret consumed bysecretKeyRef; never a literal in a repository. - Promotion: if the platform promotes new releases automatically, it
watches the publisher
snaraj/obsyncand verifies the same signing identity the OCIRepository in item 4 does. - Resources: a single replica with the
Recreatestrategy (the volumes areReadWriteOnce), and requests and limits sized to the node -- a small single-board machine wants a floor in the tens of mebibytes and a ceiling near its memory, and the server's own budget is indocs/benchmarks.md. - Deploy assurance: whatever watchdog the platform runs for drift gains this workload, so a promotion that never lands is never silent.
-
Release signals (optional): a platform whose release policy reads the replica switch or the provisioned capacity off annotations names its own domain once, in the HelmRelease values:
The chart then adds
<domain>/deployment-ready(thedeploymentReadyvalue,"true"or"false") to the Deployment and<domain>/volume-capacity(that volume'scapacity) to each claim. Left empty, the default from 1.1.4, it adds neither. A domain that is not a lower-case DNS name, or that ends inkubernetes.ioork8s.io, stops the render and names the value. Releases up to 1.1.3 always added both keys underplatform.snaraj.dev. A platform whose policy reads those keys adds exactly this line, in the same change that selects 1.1.4:Without it, 1.1.4 renders no such key and a policy that requires one refuses the release, which fails closed. The line cannot go in earlier: the 1.1.3 chart's schema refuses the unknown
platformkey.