cairn
A thin, opinionated wrapper around frappe/frappe_docker
that makes running a custom ERPNext deployment (Frappe + ERPNext + custom apps) on a
single VPS reproducible, immutable, and low-thought — without ever modifying
upstream.
Distributed as datahenge-cairn on PyPI — installs the cairn-build,
cairn-adopt, and cairn-registry commands.
Two pillars: reproducible custom image builds and a pull-based deploy lifecycle (git ref → image tag → running stack, with image-only rollback). A strict data-plane boundary keeps cairn out of your databases and volumes entirely — it ships code, not data.
Cairn: a trail marker of stacked stones. Each deploy drops a durable marker (ref → resolved commits → image tag → digest) you can navigate back to.
Three roles, one install
cairn is three commands, one package, told apart by what's configured on a machine rather than by what's installed:
Builder — cairn-build |
Target — cairn-adopt |
Registry — cairn-registry |
|
|---|---|---|---|
| Does | Builds images, pushes them, moves environment pointers | Polls for its pointer, pulls the image, converges the running stack | Provisions and operates a local OCI registry: lifecycle, retention, garbage collection |
| Reads | cairn.toml (the manifest) |
/etc/cairn/adopt.toml (a descriptor, generated by cairn-adopt examine) |
/etc/cairn/registry.toml (optional — built-in defaults otherwise) |
| Needs | Docker Engine v23+ or podman v4+, git |
Docker Engine + docker compose |
Docker Engine + docker compose, openssl |
| Credential | push access to the registry | pull-only | none — reads no manifest, no [cairn] environment |
One pip install datahenge-cairn installs all three — see Get
Started for installing it, and note that a
target simply never has a reason to run cairn-build's commands, and its pull-only registry
credential means it couldn't push or retag even if it did. cairn-registry is only needed at
all if you choose the self-hosted local-registry option (see Where your images
live) — it is independent of the other two roles and is sometimes
colocated with a builder or target, sometimes not.
Configuration
One manifest declares the image (cairn.toml, committed with the deployment); machine-local
build settings, if you need any, live separately and are never shared. See the Builder
walkthrough for provisioning one from scratch
(cairn-build setup --client <name>), and the published reference for the full manifest
schema (cairn.toml), the
machine-local /etc/cairn/builder.toml layer and its CAIRN_* environment-variable
overrides (builder.toml), and
a target's /etc/cairn/adopt.toml descriptor
(target descriptor).
Where images are pushed
Which registry you use, and who owns the credential, is worth thinking about deliberately
— especially when you're building images for a client. See Where your images
live below. cairn itself is registry-agnostic and stores no
credentials — authenticate with docker login or podman login before pushing.
How to use
On a builder — build, push, and manage environment pointers: see the Builder walkthrough and Build Automation.
On a target — adopt an existing deployment and converge it going forward: see the Target walkthrough and Reconcile Automation.
On a registry host (only if you self-host — see Where your images live): see the Registry guide.
Where your images live
cairn builds an image and puts it in a container registry; your deployment targets pull from there. cairn is registry-agnostic and assumes nothing — but which registry is not a neutral choice when you build software for clients:
📦 Choosing a container registry — start here. The image belongs in the account that owns the source; your credential should reach the engagement's images and nothing else; and what each option costs at ERPNext image sizes. Includes what to ask a client for.
🐙 GitHub Container Registry — GHCR in detail: tokens, scopes, how narrow access can be, visibility, and the deletion rule that is genuinely surprising. One option among several, not the default.
If you choose to self-host — cost dominates and off-host rollback history is genuinely not
needed — cairn-registry provisions and operates that registry: lifecycle, retention, and
garbage collection, so disk use stays bounded. See the Self-Hosted Registry
guide or cairn-registry --help.
You should never be the sole owner of a client's image. If the relationship ends, they must still be able to deploy and roll back software they own. cairn is built so the registry can be an account you do not control, and so your push credential can be scoped to one repository.
Release files for datahenge-cairn 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| datahenge_cairn-0.4.1.tar.gz | 810.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| datahenge_cairn-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:1.3 MB
Release files / datahenge_cairn-0.4.1.tar.gz
| Download URL | datahenge_cairn-0.4.1.tar.gz |
|---|---|
| Size | 810.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee39c18ebfdd8813222c7d4a1231a9cc92b1634b968d457906fa5fb6c84900e2
|
|
BLAKE2b-256 checksum How to use checksums |
f825fd0588b9e47c572d9a0b8e1ce5938bc2bdc18b194f936501034fe8fb73ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Release files / datahenge_cairn-0.4.1-py3-none-any.whl
| Download URL | datahenge_cairn-0.4.1-py3-none-any.whl |
|---|---|
| Size | 467.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f949aee4303fec5d7139d6cb73ba5ab9aa25854120f475dc2307cc244fe38df6
|
|
BLAKE2b-256 checksum How to use checksums |
e28f5951b3c54b5a8dc605f61ae07bc8473e7d5f9cc8186872b38b90da26450e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|