The official DevOps repo for running deCDN: infrastructure, deployment and day-2 tooling for operators anywhere. It serves both kinds of operator:
| You are | You run | Start here |
|---|---|---|
| a node operator | deCDN cache nodes: bond, serve, get paid per megabyte | docs/node-operators.md |
| a publisher | origin nodes for your namespaces, and optionally the services around them (sponsord and its onramp, iroh relays, an iroh DNS server) |
docs/publishers.md |
Four ways to deploy the node, cache or origin:
| Path | For | Start here |
|---|---|---|
Docker Compose (compose/), recommended |
One host that already runs Docker. The quickest start, below. Profiles for a cache node or an origin, the onboarding sponsor (sponsord + its onramp), an iroh relay, an iroh DNS server and Grafana Alloy, driven by the decdn-compose wrapper (set-up, preflight, guarded restarts). |
compose/README.md |
Ansible (ansible/) |
VMs and bare metal, one node or a fleet. Hardens the host too (firewall, SSH, patching). Also published as the decdn.node and decdn.publisher Galaxy collections. |
ansible/README.md |
cloud-init (cloud-init/) |
One VM, no control machine: paste the user-data into your provider's "create server" form. It runs the Ansible playbook on the host itself, hardening included. One template per kind of operator: a cache node, or a publisher's origin with the onboarding sponsor (sponsord + its onramp). |
cloud-init/README.md |
Helm (charts/decdn-node/) |
Kubernetes, one release per node, cache or origin. | charts/decdn-node/README.md |
The iroh relay and DNS server a publisher may run deploy with Ansible or Compose.
Supported hosts: Debian 12/13 and Ubuntu 24.04/26.04 on x86_64 or aarch64. Network,
disk and platform requirements, and how to choose a path:
docs/requirements.md.
This repo is infrastructure only. It is not a source of truth for protocol or
economic facts (chain-id, token addresses, fee splits): those trace to the deCDN ADRs
and upstream's deployment manifests. Where this repo carries one (the contract addresses
behind decdn_network), it is a generated mirror with its upstream commit recorded.
- The node, hardened.
decdn-nodeas a non-root service with a minimal privilege set: a hardened systemd unit, a locked-down container, or a restricted pod. - One public port. QUIC on udp/4433. Metrics (9090) and the admin RPC (9191) stay on loopback (on Kubernetes, behind a ClusterIP Service and a NetworkPolicy).
- The right chain config. Contract addresses come from upstream's deployment
manifest:
decdn_network: arbitrum-sepoliaon Ansible,decdn config init --chainfor Compose and Helm. Nothing is hand-copied. - Signed installs. Ansible verifies release tarballs against the GPG-signed
SHA256SUMS; Compose only takes the image by digest; Helm takes a digest (recommended) or a tag. - Monitoring. The deCDN Grafana dashboards and alert rules, with the labels they
expect: opt-in Grafana Cloud shipping via
grafana_alloyon Ansible or Compose'salloyprofile, aServiceMonitor+PrometheusRule+ dashboard ConfigMaps on Helm (monitoring/). - Day 2. Encrypted backups, restore and host migration, and a guarded
decommission:
docs/lifecycle.md.
On-chain stake and registration (ADR 019 Phase 2) is an operator step on every
path: the node serves paid traffic only after it. Upstream's decdn setup walks it
end to end (with --dry-run); this repo stops at host prep and startup.
On the host (a systemd distro with Docker, Compose v2.24+, Python 3.11+ and
age), from an account with sudo:
git clone https://github.com/decdn/devops.git && cd devops
sudo compose/decdn-compose init node --region DE # ISO 3166-1 alpha-2; account, keys, node.toml, decdn.env
# publishers: sudo compose/decdn-compose init origin --region DE --origin https://store.example.org/bucket
sudo compose/decdn-compose backup -r age1… # a key from age-keygen off the host; copy the .tar.age off too
sudoedit /etc/decdn/decdn.env # DECDN_RPC_URL=https://…
# open udp/4433 inbound: host firewall and your provider's security group
sudo compose/decdn-compose up # preflight, then docker compose up -d
sudo compose/decdn-compose health
sudo compose/decdn-compose cli whoami # the node's wallet: fund it before setup
MA=/ip4/<public-ip>/udp/4433/quic-v1 # behind 1:1 NAT too; dual-stack: add --multiaddr /ip6/…
sudo compose/decdn-compose cli setup --mbps 100 --region DE --multiaddr "$MA" --dry-run
sudo compose/decdn-compose cli setup --mbps 100 --region DE --multiaddr "$MA" # stake and register (ADR 019 Phase 2)Compose installs the node, not the host around it: firewall, SSH and patching stay
yours. The full flow (origins, the sponsor, relays, DNS, Alloy, upgrades) is in
compose/README.md. For a fleet, or a host you want hardened from
scratch, use Ansible (or cloud-init for
one VM); on Kubernetes, the Helm chart.
This is the canonical statement; the per-path READMEs add only what is specific to them.
- Nothing secret is committed. The eth keystore and the RPC URL (which may embed an
API key) are generated on, or operator-provisioned to, the target and live in
0600files readable only by whoever must read them: the service account for the keystore and its password; the service account (Ansible) or root (Compose, where Docker reads it before starting the container) for the RPC env file. On Kubernetes they are operator-created Secrets the chart only references. The repo ships*.exampletemplates for secret files only; non-secret config such ashost_vars/<node>/main.ymlis committed. The.gitignoreis a backstop, not the mechanism. Backups are encrypted on the host to public keys you choose. - Localhost-only by default. Backends bind
127.0.0.1. A service that must accept public traffic declares its port explicitly, and the node declares exactly one: udp/4433. The sponsor's public onramp (Ansible and Compose) stays on loopback too, behind Caddy on tcp/80 + tcp/443 (the default on Ansible, thecaddyprofile on Compose; otherwise you bring the proxy and open its ports). A self-hosted iroh relay (Ansible and Compose) is public by design: it terminates its own TLS on tcp/80 + tcp/443 and serves QUIC address discovery on udp/7842, with its metrics on loopback. So is a self-hosted iroh DNS server (Ansible and Compose): its own TLS on tcp/443 and DNS on udp/53 + tcp/53, with its metrics on loopback. On Kubernetes, metrics bind0.0.0.0in the pod only behind a ClusterIP Service and a NetworkPolicy. - Default-deny inbound (Ansible's
baseline, nftables; on Compose, your own firewall). SSH is the only universally open port; extra public ports are declared viabaseline_extra_inbound. - DevSec host hardening (Ansible and cloud-init;
os_hardening+ssh_hardening: key-only SSH, no root login, kernel/sysctl/PAM hardening), applied last, after the admin key is in place, so you can't lock yourself out. - Pinned supply chain. Release tarballs are GPG-verified; images, CI actions and
scanners are pinned by digest or commit SHA (
SECURITY.md,CONTRIBUTING.md).
| Path | What it is |
|---|---|
ansible/ |
The Ansible project: inventory/, playbooks/ (site.yml, node.yml for node operators, publisher.yml for publishers with origin.yml, sponsord.yml, iroh_relay.yml and iroh_dns_server.yml, backup.yml, decommission.yml), roles/ (baseline, decdn_node, grafana_alloy, sponsord, sponsord_onramp, iroh_relay, iroh_dns_server), galaxy/ (the decdn.node and decdn.publisher collections), molecule/. |
cloud-init/ |
The cloud-init deploy path: user-data-node.yaml (node operators: a cache node), user-data-publisher.yaml (publishers: an origin node, sponsord and its onramp), the on-host bootstrap.sh, and the pinned ansible-core and collections it installs. |
compose/ |
The Docker Compose deploy path: the node, sponsord, sponsord-onramp, Caddy, an iroh relay, an iroh DNS server and Grafana Alloy, one profile each, and the decdn-compose wrapper. |
charts/decdn-node/ |
The Helm chart; it renders the node's dashboards and alert rules from monitoring/. |
monitoring/ |
The deCDN Grafana dashboards and Prometheus alert rules, for the node (decdn-node/, every deploy path), sponsord (sponsord/, Ansible and Compose) and the iroh relay (iroh-relay/, Ansible and Compose). |
docs/ |
Cross-path operator docs: the two front doors (node operators, publishers), requirements, lifecycle. |
scripts/ |
Generators for the upstream mirrors (network profiles) and Compose's Alloy config (render-compose-alloy.sh), and the release gate. |
Makefile |
Lint, test and security targets; CI runs the same ones. make help lists them. |
.github/workflows/ |
CI (ci.yml, molecule.yml), releases (release-collection.yml, release-chart.yml), the weekly upstream drift check. |
CONTRIBUTING.md: local checks, everymaketarget, what CI runs, and the pinning rules.RELEASING.md: howscripts/release.shcuts a release, and how anode-collection-vX.Y.Zorpublisher-collection-vX.Y.Ztag publishes a collection and adecdn-node-X.Y.Ztag the chart.AGENTS.md: the repo's hard rules (for humans and AI agents).SECURITY.md: reporting a vulnerability, verifying releases.
Conventions: commits follow Conventional Commits. The deCDN ADRs are the only source of truth for protocol facts (payments ADR 003, node onboarding ADR 019, tokenomics ADR 026); if a doc here contradicts an ADR, fix the doc.