This walkthrough takes you from a fresh laptop to a working home-lab stack with Proxmox containers, automatic DNS, HTTPS, and single sign-on — and shows you how to plug your own web app into it. Plan for about an hour the first time through.

Before you start, you'll need: a Proxmox host on your LAN with an API token; a Namecheap-registered domain with Dynamic DNS enabled; a small Linux box (Raspberry Pi, mini-PC, or VM) to act as your edge router; and SSH access to all of them.

1. Install Celilo

The recommended path is apt on a Debian/Ubuntu box. First add the signed Celilo apt repository:

curl -fsSL https://apt.celilo.computer/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/celilo.gpg
echo "deb [signed-by=/etc/apt/keyrings/celilo.gpg] https://apt.celilo.computer stable main" | sudo tee /etc/apt/sources.list.d/celilo.list
sudo apt update

A management server, in one command

If this box is going to be your home-lab's Celilo management server, install celilo-bootstrap:

sudo apt install -y celilo-bootstrap

That's the whole setup — zero prompts. The package installs the Celilo CLI (as an unprivileged celilo user, with state under /var/celilo), then stands the box up as a management server: it discovers its own internal network and DNS, generates the fleet SSH key, registers itself in its own machine pool, and deploys the celilo-mgmt module to itself over a local connection. The deploy runs in the background moments after the install finishes (Debian packaging forbids running it inline). Watch it converge:

celilo module list          # celilo-mgmt → DEPLOYED
celilo system config get    # discovered internal subnet + DNS, fleet ssh.public_key

Network zones beyond internal (DMZ, app, secure) appear later, when you deploy a firewall module — Celilo never guesses your addressing. Internet access is assumed (every box has it); zones are what progressively unlock.

Just the CLI

On a client box, CI runner, or a machine that only needs to talk to a management server, install the CLI alone — no self-deploy:

sudo apt install -y celilo
celilo --version
celilo system init           # interactive: network config + SSH key

No apt? (macOS, or a quick CLI on a laptop)

Where apt isn't available, the curl installer puts the CLI on your $PATH via Bun:

curl -fsSL https://celilo.computer/install.sh | bash

It installs Bun (if missing) plus @celilo/cli, @celilo/event-bus, and @celilo/e2e, prints the $PATH line for your shell rc, and lists next steps — it does not run the interactive system init or install the event-bus daemon. View the source before piping it to your shell if you like.

You can re-run system init any time — take all defaults, or override specific values inline:

celilo system init --accept-defaults
celilo system init network.dmz.subnet=10.0.10.0/24

2. Add a Proxmox service

A service is an infrastructure provider Celilo can provision new compute on — Proxmox creates LXC containers; a Digital Ocean account would create droplets; another adapter could provision VMs on bare-metal libvirt or anything else with an API. Modules declare what they need (CPU, memory, disk, zone) and the service provides something that meets those requirements. Proxmox is the path this walkthrough takes; the abstraction is open for extension.

Before running the next command, create an API token in the Proxmox UI (Datacenter → Permissions → API Tokens) and copy the secret somewhere safe — Proxmox only shows it once.

celilo service add proxmox

You'll be prompted for:

  • A human-readable name (e.g. Proxmox Home Lab)
  • Which zones this service can host containers in (multi-select: dmz, app, secure, internal)
  • Proxmox IP, API port (default 8006), token ID (default root@pam!celilo) and the token secret you just created
  • Default target node (default pve) and storage (default local-lvm)
  • An Ubuntu LTS version for the container template (24.04 / 22.04 / 20.04)

Celilo tests the API connection at the end and offers to download the chosen Ubuntu template into Proxmox so it's ready when modules need it.

3. Set up the Namecheap registrar

The namecheap module provides the dns_registrar capability — every other module that needs a public DNS record (Caddy, Authentik, your web app) will reach for it automatically.

First, in the Namecheap UI, enable Dynamic DNS for each domain you want Celilo to manage and copy the DDNS password it generates (Domain List → Advanced DNS → Dynamic DNS).

celilo module import namecheap
celilo module generate namecheap

module generate prompts for any required config or secrets it doesn't already have:

  • domains — the list of domains you'll manage. The first one is treated as primary.
  • ddns_passwords — a JSON object mapping each domain to its DDNS password.

You can also set values non-interactively:

celilo module config set namecheap domains '["example.com"]'
celilo module config set namecheap ddns_passwords '{"example.com":"<your-ddns-password>"}'

Then deploy:

celilo module deploy namecheap

4. Add an edge router and install iptables

The iptables module turns a small Linux box into your home-lab router and provides the firewall capability — port forwarding, NAT, and DNAT for every service you expose. Unlike Caddy or Authentik (which Celilo provisions as new Proxmox containers), iptables runs on an existing machine, because that machine is the network boundary.

Register the router as a machine. Replace the IP with whatever your router box uses:

celilo machine add 192.168.1.1 --ssh-user root --earmark iptables

Celilo SSHes in, auto-detects hostname, OS, CPU, memory, network interfaces, and figures out which zone each interface belongs to based on your subnet config. The --earmark iptables flag reserves this machine for the iptables module so no other module tries to land on it.

Then install and deploy:

celilo module import iptables
celilo module generate iptables
celilo module deploy iptables

You'll be asked which zones this firewall services (typically all of dmz, app, secure, internal). The NAT IP and per-zone IPs are derived automatically from the machine you just added.

Why a real machine? Caddy and Authentik are workloads — Celilo builds them fresh each time. The firewall is infrastructure — it has to exist before anything can route through it, so you bring your own.

5. Deploy Caddy and Authentik

Now the foundations are in place: Proxmox can spawn containers, Namecheap can publish DNS, and iptables can forward traffic. Caddy and Authentik just need a few app-specific values and they'll land on their own.

Caddy — HTTPS reverse proxy

celilo module import caddy
celilo module generate caddy
celilo module deploy caddy

You'll be asked for:

  • hostnames — FQDNs Caddy should serve (e.g. ["www.example.com"]). The first is canonical.
  • acme_email — contact email Let's Encrypt uses for cert notifications.

That's it. Celilo provisions an LXC container on Proxmox in the dmz zone, calls firewall.exposeService on the iptables module to open ports 80/443 and DNAT them inbound, calls dns_registrar.registerHost on namecheap to point each hostname at your public IP, and lets Caddy fetch real Let's Encrypt certs over the resulting public path.

Authentik — single sign-on

celilo module import authentik
celilo module generate authentik
celilo module deploy authentik

The only thing Authentik insists on is admin_email. Database password, bootstrap token, and signing key are auto-generated and stored encrypted; the hostname (auth.<your-primary-domain>), ACME cert, and DNS record come from Caddy and Namecheap automatically.

When the deploy finishes, log into https://auth.your-domain.com with the admin email and the password Celilo printed during generation.

6. Write a web app that uses these capabilities

There's no scaffolder yet — the simplest path is to copy an existing module directory and edit it. modules/celilo-website is a good reference: it's a static Astro site that uses public_web for hosting and dns_registrar for its hostname.

The minimum manifest for an app that wants HTTPS, DNS, and SSO looks like this:

# my-app/manifest.yml
celilo_contract: "1.0"
id: my-app
name: My App
version: 1.0.0
description: "A web app behind Caddy and Authentik"

requires:
  capabilities:
    - name: public_web         # Caddy gives me HTTPS + a route
      version: 3.0.0
    - name: dns_registrar      # Namecheap gives me a public hostname
      version: 4.0.0
    - name: idp                # Authentik gives me OIDC sign-in
      version: 1.0.0

variables:
  owns:
    - name: subdomain
      type: string
      required: true
      description: "Subdomain to serve on (e.g. 'app' for app.example.com)"
      source: user
  imports: []

secrets:
  declares: []

hooks:
  on_install:
    script: ./scripts/on-install.ts
    timeout: 120000
  health_check:
    script: ./scripts/health-check.ts
    timeout: 60000

The on_install hook is where you wire the capabilities together. Each capability arrives as a typed function on ctx.capabilities:

// my-app/scripts/on-install.ts
import type { HookContext } from '@celilo/capabilities';

export default async function onInstall(ctx: HookContext) {
  const fqdn = `${ctx.config.subdomain}.${ctx.capabilities.dns_registrar.primary_domain}`;

  // 1. Open a public DNS record pointing at our firewall's external IP
  const { ip } = await ctx.capabilities.firewall.exposeService({
    internal_host: ctx.machine.hostname,
    internal_port: 8080,
    public_port: 443,
  });
  await ctx.capabilities.dns_registrar.registerHost({ host: fqdn, ip });

  // 2. Register an OIDC client with Authentik
  const oidc = await ctx.capabilities.idp.createOidcClient({
    name: 'my-app',
    redirect_uris: [`https://${fqdn}/auth/callback`],
  });

  // 3. Tell Caddy to reverse-proxy https://<fqdn> to my container
  await ctx.capabilities.public_web.registerRoute({
    hostname: fqdn,
    upstream: `http://${ctx.machine.hostname}:8080`,
  });

  // oidc.client_id and oidc.client_secret are now available to your app
}

Build, import, and deploy your module:

celilo module pack ./my-app          # creates my-app-1.0.0+1.netapp
celilo module import ./my-app-1.0.0+1.netapp
celilo module generate my-app
celilo module deploy my-app

When the deploy finishes you'll have a public URL with HTTPS, a DNS record that survives IP changes, and an OIDC integration with Authentik — none of which you had to configure by hand.

What's next? Read the module catalogue for the full list of capabilities you can compose, or jump to Authoring modules below for hooks, secrets, and Terraform templates.

Core concepts

Modules

A module is a self-contained infrastructure unit — a DNS server, a reverse proxy, an identity provider. Each module declares its capabilities, requirements, and secrets. Celilo handles dependency resolution, provisioning, and wiring.

Capabilities

Capabilities are typed interfaces between modules. A module that provides dns_registrar can register DNS records; any module that requires it gets that function injected automatically. No hardcoded hostnames or IPs needed.

Machines and services

A machine is an existing host (Raspberry Pi, VPS, bare metal) that Celilo can SSH into. A service is a container host (Proxmox, Digital Ocean) that Celilo can provision new containers on. Modules run on whichever infrastructure is available in their zone.

Module commands

celilo module list              # List installed modules
celilo module import <name>   # Install from registry
celilo module import <path>    # Import local module
celilo module remove <name>    # Remove a module
celilo module config <name>    # Edit module configuration
celilo module health <name>    # Run health check
celilo module deploy <name>    # Re-run on_install hook

Service commands

celilo service list             # List container services (Proxmox, DO...)
celilo service add              # Add a container service
celilo service remove <name>

Machine commands

celilo machine list             # List known machines
celilo machine add              # Add a machine (VPS, Pi, bare metal)
celilo machine remove <host>

Authoring modules

A Celilo module is a directory with a manifest.yml and optional TypeScript hook scripts, Terraform templates, and Ansible playbooks.

my-module/
├── manifest.yml          # Module declaration
├── celilo/
│   └── types.d.ts        # Generated config types
├── scripts/
│   ├── package.json
│   ├── on-install.ts     # Hook scripts (TypeScript)
│   └── health-check.ts
├── terraform/            # Terraform templates (.tpl)
└── ansible/              # Ansible playbooks

Package your module for distribution:

celilo module build my-module   # Runs build.command from manifest
celilo module pack my-module    # Creates my-module-1.0.0+1.netapp

Built-in capabilities

  • public_web — HTTPS reverse proxy and static site hosting (Caddy)
  • dns_registrar — DNS A record registration (Namecheap, others)
  • dns_internal — Internal DNS resolution (Knot + Unbound)
  • firewall — Port forwarding and NAT (Greenwave, iptables)
  • idp — OIDC identity provider (Authentik)