A small binary that runs where your data lives.
TillArk never touches your backup bytes; the agent does. It runs on a host that can reach your databases and your storage, claims jobs, runs the backup engines, encrypts before upload, and reports a signed manifest. One agent can serve many sources; run more for more regions or networks.
Claim, run, sign, report
start ─▶ register keys ─▶ wait for approval ─▶ heartbeat ─▶ claim a job
(what this host can run)
┌──────────────────────────────┬──────────────────────────────┐
backup verify restore
│ │ │
open this job's credentials check the manifest signatures check the manifest signatures
check the bucket's object lock restore into a throwaway server check the stored hash
dump or stream the source run the checks, then remove it restore into the destination
encrypt, upload │
sign the manifest │ │
└──────────────────────────── report ────────────────────────┘Every heartbeat reports which source types the host can back up, verify and restore, from the programs it finds. TillArk sends a job only to an agent that can run it, and routes WAL-G, pgBackRest and clickhouse-backup work for a database on a particular host to the agent on that host. A reference check after a run is restored goes to an agent that reaches both restored stores.
Install
Builds are published for Linux and macOS on amd64 and arm64: one static binary, no runtime to install. The install script picks the build for your host, verifies the release signature and the checksum, and stops if either fails. It needs curl and OpenSSH 8.2 or newer.
curl -fsSLO https://tilldev.dev/downloads/ark-agent/install.sh
less install.sh # read it first
sh install.sh # installs /usr/local/bin/ark-agentPin a version with ARK_AGENT_VERSION=<version> sh install.sh, or install somewhere else with ARK_AGENT_DIR=$HOME/bin. To verify by hand:
V=$(curl -fsSL https://tilldev.dev/downloads/ark-agent/LATEST)
F=ark-agent_${V}_linux_amd64 # or linux_arm64, darwin_arm64, darwin_amd64
curl -fsSLO https://tilldev.dev/downloads/ark-agent/allowed_signers \
-O https://tilldev.dev/downloads/ark-agent/$V/SHA256SUMS \
-O https://tilldev.dev/downloads/ark-agent/$V/SHA256SUMS.sig \
-O https://tilldev.dev/downloads/ark-agent/$V/$F
# The signature proves TillDev built this list; the checksum ties your file to it.
ssh-keygen -Y verify -f allowed_signers -I releases@tilldev.dev -n ark-agent-release \
-s SHA256SUMS.sig < SHA256SUMS
grep " $F\$" SHA256SUMS | sha256sum --check # macOS: shasum -a 256 --check
sudo install -m 0755 "$F" /usr/local/bin/ark-agentRun it as a service
Mint a token with tilldev ark tokens create --scope operator. An operator token lets the agent claim and report jobs but not change your sources, targets or policies. Run the agent as its own unprivileged user: PostgreSQL and MySQL verification refuse to run as root.
# /etc/systemd/system/ark-agent.service
[Unit]
Description=TillArk agent
After=network-online.target
Wants=network-online.target
[Service]
User=ark-agent
EnvironmentFile=/etc/ark-agent/env
ExecStart=/usr/local/bin/ark-agent
Restart=on-failure
RestartSec=5
StateDirectory=ark-agent
StateDirectoryMode=0700
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
[Install]
WantedBy=multi-user.targetsudo useradd --system --home-dir /var/lib/ark-agent --shell /usr/sbin/nologin ark-agent
sudo install -d -m 0700 /etc/ark-agent
sudo tee /etc/ark-agent/env >/dev/null <<'EOF'
TILLARK_CP_URL=https://ark.tilldev.dev
TILLARK_AGENT_TOKEN=tark_…
EOF
sudo chmod 0600 /etc/ark-agent/env
sudo systemctl daemon-reload
sudo systemctl enable --now ark-agent
journalctl -u ark-agent | grep key_fingerprintIf the agent restores files, add each of its TILLARK_FILE_ROOTS to the unit as ReadWritePaths=. Outside systemd, run ark-agent with the same environment; it stops cleanly on SIGTERM.
ark-agent fingerprint, with the one under TillArk → Agents, then approve it there or with tilldev ark agents approve <id> --fingerprint <fp>. New keys (a rebuilt host, a wiped data directory) revoke the approval.Work only TillArk signed
The agent acts only on work signed by TillArk’s control-plane key: jobs, WAL archiving leases and clone teardowns. Each reply is signed with Ed25519 and ML-DSA-65 over its body and a nonce the agent sent, so a reply can’t be altered, replayed or moved to another request. Both signatures must verify.
On first contact the agent records the key in cp-key.pin in its data directory and logs the fingerprint. From then on it refuses work under any other key. To pin it before first contact, set TILLARK_CP_KEY to the fingerprint under TillArk → Agents or from tilldev ark cp-key; the install steps on the Agents page put it in the run command.
cp-key.pin. An agent pinned with TILLARK_CP_KEY follows too, and logs a reminder to update the variable; until you do, TillArk → Agents and tilldev ark cp-key list it as restarting on another key. A key change without that endorsement is refused, and stops the agent at start with exit code 2.Environment variables
| Variable | Meaning |
|---|---|
TILLARK_CP_URL | TillArk’s agent endpoint, https://ark.tilldev.dev. Must be https. Required. |
TILLARK_AGENT_TOKEN | The tark_… token. Required. |
TILLARK_CP_KEY | The control plane’s key fingerprint, in full or its grouped first 32 characters. Unset, the agent trusts the key it meets first and records it. See Work only TillArk signed. |
TILLARK_AGENT_NAME | Name shown under Agents. Default: the host name. |
TILLARK_AGENT_PLATFORM | Platform label shown under Agents. Default: the OS and architecture. |
TILLARK_AGENT_DATA_DIR | The agent key, engine caches and scratch space. Default /var/lib/ark-agent. Never backed up or restored into. |
TILLARK_AGENT_KEY_PATH | Where the agent key lives, if not in the data directory. |
TILLARK_ENGINES | Engines this host runs, comma-separated: restic, walg, pgbackrest, chbackup. Default restic. |
TILLARK_LOCAL_SOURCES | Source ids whose database or git node runs on this host. Their WAL-G, pgBackRest or clickhouse-backup backups, WAL streaming, and a git node’s backups and verifications, run only here; needs the source’s engine in TILLARK_ENGINES. A restore onto a git node runs on the host listing the destination. See point-in-time recovery, ClickHouse table parts and TillForge git nodes. |
TILLARK_FILE_ROOTS | Absolute directories files sources may read and restore into, and bucket backups may restore into. Unset, the agent backs up no files. |
TILLARK_BYOK_DIR | Absolute directory of the customer keys this host holds. Default <data directory>/byok. |
TILLARK_BYOK_ONLY | Set 1 and the agent takes only jobs under a customer key, never one under a key TillArk holds. |
TILLARK_FAILOVER_CONFIG_PATH | The failover configuration for sources this host can promote. See failover. |
TILLARK_OBSERVE | Primaries this host observes for automatic failover, as source-id=ENV_VAR pairs separated by commas; each variable holds that primary’s connection string. The agent must be approved. See automatic failover. |
TILLARK_OBSERVER_VANTAGE | app when this host runs where your application does, else infra (the default). |
TILLARK_OBSERVE_INTERVAL_MS | Time between observations, 1000 to 15000. Default 5000. |
TILLARK_PEER_LISTEN | IP address and port this host takes observers’ signed reports on, such as 0.0.0.0:7443, over TLS 1.3 with the certificate it signs in with. Set it on a host that watches a replica, so the replica can fail over while TillArk is unreachable. |
TILLARK_PEER_ADDR | The host:port observers reach this host at. Required when TILLARK_PEER_LISTEN is 0.0.0.0 or ::. |
TILLARK_PRIVATE_TARGETS | Comma-separated host names, IPs or CIDRs of private storage this host may reach, such as a MinIO on your network. See storage on your network. |
TILLARK_CLONE_LISTEN | IP address masked clones listen on, 0.0.0.0 for every interface. Set, this host builds and serves clones. See hosting clones. |
TILLARK_CLONE_HOST | The name or address people reach clones by; each clone’s certificate names it. Required when TILLARK_CLONE_LISTEN is 0.0.0.0 or ::. |
TILLARK_CLONE_PORTS | Ports clones are served on, one each. Default 55400-55499. |
TILLARK_CLONE_MAX | Clones served at once, 1 to 16. Default 2. |
TILLARK_CA_FILE | Extra trusted root certificates, for storage or a control-plane endpoint behind a private CA (such as a TLS-inspecting proxy). |
TILLARK_CLIENT_CERT_PATHTILLARK_CLIENT_KEY_PATH | A client certificate and key to present to TillArk instead of the agent’s own. Set both or neither. Unset, the agent creates client-cert.pem and client-key.pem in its data directory on first start; ark-agent client-cert prints its SHA-256. |
TILLARK_PG_BIN_DIR | Where the PostgreSQL programs are, if not on PATH. |
TILLARK_PGBACKREST_BIN_DIR | Where pgbackrest is, if not on PATH. |
TILLARK_CHBACKUP_BIN_DIR | Where clickhouse-backup is, if not on PATH. |
TILLARK_MYSQL_BIN_DIR, TILLARK_MONGO_BIN_DIR, TILLARK_REDIS_BIN_DIR, TILLARK_MEILI_BIN_DIR, TILLARK_CLICKHOUSE_BIN_DIR, TILLARK_FORGE_BIN_DIR | The same, per source type. |
TILLARK_JOB_TIMEOUT_MINUTES | Longest one job may run. Default 720, minimum 5. |
TILLARK_POLL_INTERVAL_MS | How often to ask for work. Default 10000, minimum 2000. |
TILLARK_HEARTBEAT_INTERVAL_MS | How often to report in. Default 30000, minimum 5000. |
TILLARK_ALLOW_INSECURE_LOCAL | Set 1 to allow an http control plane, storage, failover provider API, Cloudflare API or switch webhook on localhost, and a control plane that signs no work. Local testing only. |
The startup log prints the configuration without the token.
What each source type needs on the host
Dumps and files are stored with restic 0.17 or newer, which must be on PATH. PostgreSQL sources on the WAL-G engine use WAL-G 3.0 or newer instead, the PostgreSQL build (wal-g-pg), and those on the pgBackRest engine use pgBackRest 2.51 or newer. ClickHouse sources on the clickhouse-backup engine use clickhouse-backup 2.5 or newer, and their archives are stored with restic. Each source type then needs:
| Source | Needs |
|---|---|
| PostgreSQL | Pg_dump and pg_restore; the PostgreSQL server (initdb, postgres) to verify. Set TILLARK_PG_BIN_DIR if they are not on PATH. |
| MySQL / MariaDB | Mysqldump and mysql (or mariadb-dump and mariadb); mysqld (or mariadbd and mariadb-install-db) to verify. Set TILLARK_MYSQL_BIN_DIR if they are not on PATH. |
| MongoDB | Mongodump and mongorestore (MongoDB Database Tools); mongod as well to verify and restore. Set TILLARK_MONGO_BIN_DIR if they are not on PATH. |
| ClickHouse | The clickhouse binary (or clickhouse-local) to back up and verify; with the clickhouse-backup engine, clickhouse-backup 2.5 or newer on the database host and the clickhouse binary to verify and restore. Set TILLARK_CLICKHOUSE_BIN_DIR and TILLARK_CHBACKUP_BIN_DIR if they are not on PATH. |
| Meilisearch | The meilisearch binary, to verify only (backups and restores use the HTTP API). Set TILLARK_MEILI_BIN_DIR if it is not on PATH. |
| Redis / Valkey | Redis-server (or valkey-server), to verify only (backups and restores use the Redis protocol). Set TILLARK_REDIS_BIN_DIR if it is not on PATH. |
| Files or bucket | Nothing beyond restic. |
| TillForge git node | The git node's own tillforge and git, so the agent runs on the node. Set TILLARK_FORGE_BIN_DIR if tillforge is not on PATH. |
PostgreSQL’s pg_dump must be at least as new as the server. TillArk → Agents shows what each host can run; a job goes only to an agent that has its programs.
Keys that stay on your hosts
A source can be put under a customer key: 32 random bytes kept in a file on your agent hosts. The agent derives each repository’s password from it, so TillArk never holds a key that opens those backups. See Security for what that protects.
# As the agent's user, so the agent can read the key
sudo -u ark-agent ark-agent byok-keygen # prints the key's fingerprint
sudo -u ark-agent ark-agent byok-list # every key this host holds
# Copy it to each other host that runs the source's jobs
sudo cat /var/lib/ark-agent/byok/<fingerprint>.key # on this host
sudo -u ark-agent ark-agent byok-import < <fingerprint>.key # on the other host
# Then put the source under it
tilldev ark sources update <source-id> --byok <fingerprint>- Run the commands as the user the agent runs as. A key file is readable by its owner only, so a key written by root is one the agent can’t use. If you set
TILLARK_AGENT_DATA_DIRorTILLARK_BYOK_DIR, set it for these commands too. - Every host that runs the source’s backups, verifications, restores or WAL archiving needs the key. The agent reports the keys it holds with each heartbeat, so a new key counts from its next heartbeat, with no restart. TillArk sends a source’s jobs only to agents holding its key, and refuses to put a source under a key no approved agent online holds.
byok-listnames any file it can’t use (another user can read it, it isn’t named after its fingerprint, or its contents don’t match) and exits 1. The agent logs the same and skips it.byok-importreads the key as base64 or hex. The filebyok-keygenwrites is base64.
On a host that must never run a job under a key TillArk holds, set TILLARK_BYOK_ONLY=1. TillArk then sends it only customer-key work, and TillArk → Agents marks it as customer-key only.
Storage on your network
A target can be a MinIO, another S3-compatible store or an SFTP server on your own network: a private address, or a name outside public DNS (a single label, or one ending in .internal, .local, .lan, .home.arpa or .corp). List it on each agent host that can reach it:
# Storage at 10.20.0.7, or anywhere in that network
TILLARK_PRIVATE_TARGETS=10.20.0.0/16
# An internal name is listed by name
TILLARK_PRIVATE_TARGETS=minio.internal,10.20.0.0/16- Each agent reports its list, and TillArk sends a private target’s backups, verifications, restores and deletions only to agents whose list covers it. Under TillArk → Targets, each private target names the agents its jobs go to, or says that none can take them.
- List a name ending in
.internalor.localby name: the agent refuses it before resolving it, so an address range doesn’t cover it. Other names are covered by their name, or by the network they resolve into. - For a name that looks public but resolves to a private address, mark the target private (TillArk → Targets, or
--privateontilldev ark targets create) and list the name or its network. - The host that archives a source’s WAL, the one listing it in
TILLARK_LOCAL_SOURCES, must reach every target the source uses. - A bucket source on such a host follows the same rule: its backups, and real restores into it, go only to agents that list its endpoint. Mark it private under TillArk → Sources, or with
--privateontilldev ark sources create.
Loopback, link-local and cloud metadata addresses can’t be targets. The agent never connects to a link-local or metadata address, even one inside a network you list.
Hosting masked clones
An agent can build masked clones of PostgreSQL backups and serve them to your team. Rows are masked on this host as they are copied into the clone; they never pass through TillArk.
# On a host with the PostgreSQL server installed
TILLARK_CLONE_LISTEN=0.0.0.0 # the address clones listen on
TILLARK_CLONE_HOST=clones.internal # the name your team connects to
TILLARK_CLONE_PORTS=55400-55499 # the default
TILLARK_CLONE_MAX=2 # clones served at once; the default- The host needs what verifying PostgreSQL needs:
pg_restoreand the server (initdb,postgres), 12 or newer and at least as new as the sources you clone, andresticinTILLARK_ENGINES. - Each clone’s certificate names
TILLARK_CLONE_HOST, and the connection TillArk hands out checks it. Leave it unset only whenTILLARK_CLONE_LISTENis one address people reach directly. - A clone takes one port from the range. Open the range to the machines that use clones and no further. Each clone accepts only TLS, only its own user and database, with a password.
- Clones live under
<data directory>/clonesand keep running across restarts of the agent. One that expires, or that someone expires early, is deleted at the agent’s next heartbeat. - Restart the agent after setting these. TillArk → Agents then shows the host as hosting clones.
Security properties
| Property | How |
|---|---|
| Pinned client certificate | The agent presents its own certificate on every connection. TillArk pins the first one it sees and refuses the agent’s token with any other certificate, or none, so a stolen token alone can’t act as the agent. After rebuilding a host, reset the pin under TillArk → Agents or with tilldev ark agents reset-cert <id>. |
| Signed work | The agent acts only on work the control-plane key it trusts signed, with Ed25519 and ML-DSA-65 both verifying, bound to the reply and to a nonce it sent. |
| Credentials for one job | Database and storage credentials arrive sealed to this agent’s key (X-Wing: ML-KEM-768 + X25519), bound to one job, and are opened only in memory. |
| Encrypted before upload | restic, WAL-G and pgBackRest encrypt every byte in the agent (clickhouse-backup’s archives are stored with restic); your storage provider holds only ciphertext. pgBackRest’s AES-256-CBC isn’t authenticated, so a restore checks every file against checksums the signed manifest covers, and each WAL segment against the SHA-1 it was archived under. |
| Customer keys stay local | A customer key never leaves the host. Only its fingerprint is sent to TillArk; repository passwords are derived from it in the agent, per job. |
| Signed manifests | Each backup’s manifest is signed with Ed25519 and ML-DSA-65; TillArk rejects it unless both verify. |
| No shell | Engines run from an argument list with a minimal environment, capped output and a timeout. Secrets go through the environment or owner-only files, never the command line, and are redacted from errors. |
| Guarded egress | Storage traffic goes through a proxy inside the agent that resolves each address and connects only to the one it checked. Private addresses need TILLARK_PRIVATE_TARGETS; link-local and cloud metadata addresses never pass, even when listed. |
| No partial backups | A dump that fails midway, or a files backup that could not read every file, leaves no snapshot and is reported failed. |
| Scoped deletion | Retention deletes only the snapshot tagged with the expired backup, and nothing if the snapshot list could not be read in full. |
| HTTPS to TillArk | TLS 1.2 or newer, with a post-quantum key exchange offered on TLS 1.3. A redirect to http or another host is refused, so the token can’t leak. |
See the quickstart to protect a first source, Security for the key model, or the CLI reference. Back to the TillArk overview.