Deploying Agents¶
An agent container runs nginx, dnsmasq, and acme.sh for a site. Deploy one agent per site — a site is one subnet, and the agent is that subnet's single DHCP/DNS and reverse-proxy authority, so running more than one per site on a shared L2 would collide. The agent also provisions persistent volume directories for the whole site (see Volume storage below).
Agents are deployed manually in Proxmox (not through the manager UI) and should be set up after configuring the site in the manager but before importing the node. This ensures DNS and reverse proxy services are running when the node comes online.
Prerequisites¶
- Management container deployed and running (Installation Guide)
- A site configured in the manager
- An API key generated from an admin account
- SSH or web UI access to the target Proxmox host
Workflow¶
graph LR
A[Configure site<br/>in manager] --> B[Generate<br/>admin API key] --> C[Deploy agent<br/>on Proxmox node] --> D[Verify agent<br/>checks in] --> E[Import node<br/>in manager]
classDef step fill:#f9f9f9,stroke:#333,stroke-width:1px
class A,B,C,D,E step
1. Pull the Agent Image¶
On the target Proxmox host:
apt update && apt install -y skopeo
skopeo copy docker://ghcr.io/mieweb/opensource-server/agent:latest \
oci-archive:/var/lib/vz/template/cache/opensource-server-agent.tar
2. Create the Agent Container¶
In the Proxmox web interface (https://your-proxmox-host:8006):
- Create CT on the target node
- Template: Select
opensource-server-agent - Network: Configure with a static IP in the site's subnet
- Resources: Allocate CPU, memory, and storage as needed
Alternatively, use pct create from the command line:
pct create <vmid> /var/lib/vz/template/cache/opensource-server-agent.tar \
--hostname agent \
--net0 name=eth0,bridge=vmbr0,ip=<static-ip>/24,gw=<gateway> \
--memory 512 \
--features nesting=1 \
--unprivileged 1
3. Configure Environment Variables¶
Set SITE_ID, MANAGER_URL, and API_KEY via the Proxmox LXC configuration. These propagate to /etc/environment on boot via the base image's environment.sh service.
Add to /etc/pve/lxc/<vmid>.conf:
lxc.environment = SITE_ID=<site-id>
lxc.environment = MANAGER_URL=http://<manager-ip>:3000
lxc.environment = API_KEY=<admin-api-key>
| Variable | Description |
|---|---|
SITE_ID |
Numeric site ID from the manager (visible in the URL when viewing the site) |
MANAGER_URL |
Base URL of the manager container (e.g., http://192.168.1.10:3000) |
API_KEY |
API key from an admin account. Used to authenticate check-ins. |
Volume storage for persistent volumes¶
If the site uses persistent volumes, the agent creates each volume's host directory on check-in. For that to work, the site's volumes root must be visible and writable inside the agent container, and it must be on storage shared across every node so a directory the agent creates exists wherever a container lands.
The volumes root is <volume-storage-path>/volumes — where <volume-storage-path>
is the configured path of the node's volume storage (e.g. a CephFS/NFS mount
like /mnt/pve/cephfs). Do the following once per site, on the Proxmox host that
runs the agent:
- Pre-create the volumes root on the shared storage, owned by the
unprivileged-container id-mapped root (host UID/GID
100000), so the agent — itself an unprivileged CT mapped the same way — can create and own per-volume subdirectories:
# <volumes-root> e.g. /mnt/pve/cephfs/volumes
mkdir -p <volumes-root>
chown 100000:100000 <volumes-root>
chmod 0770 <volumes-root>
- Bind-mount the volumes root into the agent container at the same path so
the host path the manager derives resolves identically inside the agent. Add
to
/etc/pve/lxc/<agent-vmid>.conf:
mp0: <volumes-root>,mp=<volumes-root>
For example: mp0: /mnt/pve/cephfs/volumes,mp=/mnt/pve/cephfs/volumes.
The agent checks for this mount: it only creates a volume directory if the
path lies on a mounted filesystem other than its own root. If the volumes
root isn't mounted, the volume is reported as failed with a message saying
so, instead of being created inside the agent container where Proxmox can't
see it.
Why the agent — not a per-node host process — creates these
There is one agent per site, and the shared volumes root is bind-mounted
into it, so this single agent provisions every site volume's directory
(which is why the volumes root must be on shared storage). The agent chowns
each new directory to the consuming containers' id-mapped root (host UID/GID
100000) best-effort. In an unprivileged agent guest — the norm, since
pct create defaults to --unprivileged 1 (this includes the embedded
Manager agent) — the agent's root maps to host 100000, so mkdir already
yields the right owner and the chown is a tolerated no-op (EINVAL/EPERM).
In a privileged agent guest (only if you deliberately create it with
--unprivileged 0) the agent is host root, so the chown is what makes the
directory writable by the unprivileged consumer.
Custom id-maps are not supported for volumes
Volume ownership assumes the default Proxmox unprivileged-CT id-map,
which maps guest UID/GID 0 to host 100000. The manager always advertises
100000 as the owner and the agent chowns each volume directory to it, so
read-write volumes are writable by the consuming containers' mapped root.
Sites that override this with a custom lxc.idmap (a different base) are
not supported for persistent volumes: the per-volume directories would
still be owned by 100000 and would not be writable inside those
containers. Keep containers that use volumes on the default id-map.
4. Start and Verify¶
pct start <vmid>
Verify the agent is checking in and applied its configs:
# Enter the container
pct enter <vmid>
# Timer and last runs
systemctl status opensource-agent.timer
journalctl -u opensource-agent.service
# Check that configs were applied
cat /etc/nginx/nginx.conf
cat /etc/dnsmasq.conf
# Run a check-in manually
systemctl start opensource-agent.service
The agent also appears on the manager's Agents page (/agents, admin only) with its last check-in time and service health.
5. Forward Network Traffic¶
Forward the following ports from the Proxmox host to the agent container:
| Port | Protocol | Service |
|---|---|---|
| 80 | TCP | HTTP (nginx) |
| 443 | TCP | HTTPS (nginx) |
| 53 | TCP/UDP | DNS (dnsmasq) |
6. Import the Node¶
With the agent running, proceed to import the node in the manager. The agent's dnsmasq and nginx will automatically receive updated configurations as containers are created and removed.
How It Works¶
A systemd timer launches the agent every 30 seconds. It checks in with the manager, reporting host info and service status, and receives the site's config snapshot in return — see the agent developer reference for the protocol and apply/rollback details.
sequenceDiagram
participant Timer as systemd timer
participant Agent
participant Manager
Timer->>Agent: start (every 30s)
Agent->>Manager: POST /api/v1/agents<br/>Authorization: Bearer {API_KEY}<br/>If-None-Match: {etag}
alt Config changed
Manager-->>Agent: 200 OK + config snapshot + ETag
Agent->>Agent: Render, validate (nginx -t), apply + reload
Agent->>Manager: POST again (reports apply results)
Manager-->>Agent: 304 Not Modified
else No changes
Manager-->>Agent: 304 Not Modified
end
Each check-in is recorded by the manager, so admins can monitor agent health on the Agents page. ETag caching keeps unchanged configs to a single 304 round trip. Dnsmasq's main config triggers a full restart when it changes, while the auxiliary files (DHCP hosts, host records, options, upstream servers) only trigger a SIGHUP reload.