Skip to main content

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.

📖 Documentation

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:

Buildercairn-build Targetcairn-adopt Registrycairn-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 docs/technical/ABOUT_REGISTRIES.md for the tradeoffs, and docs/technical/ABOUT_GHCR.md for GitHub's registry specifically. 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 (a full walkthrough isn't published yet — this is everything there is to know for now):

cairn-adopt examine             # describe this host's running stack (one-time, or after a manual change)
cairn-adopt systemd-units       # print the reconcile service + timer; review, then install them
cairn-adopt reconcile --dry-run # see what would change
cairn-adopt reconcile           # converge to whatever the environment's pointer says

reconcile is idempotent and meant to run on a timer — with nothing to do, it does nothing. It never rolls back on failure; it stops and reports, because a failed bench migrate is not something to silently reverse.

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:

📦 docs/technical/ABOUT_REGISTRIES.md — 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.

🐙 docs/technical/ABOUT_GHCR.md — GitHub's registry 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 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.3.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for datahenge-cairn 0.3.3
File Size Uploaded
datahenge_cairn-0.3.3.tar.gz 698.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for datahenge-cairn 0.3.3
File Interpreter ABI Platform
datahenge_cairn-0.3.3-py3-none-any.whl Python 3 none any Details

Total release size:1.2 MB

Release files / datahenge_cairn-0.3.3.tar.gz

Download URL datahenge_cairn-0.3.3.tar.gz
Size 698.5 kB
Tags Source
SHA-256 checksum
How to use checksums
d3b400a8e1d0414ec8204ac54a32627d850f0374b2ecbb57f6b454e6066bd788
BLAKE2b-256 checksum
How to use checksums
82a4f3dda6fefd9dd184e377d69bfed052f30c4f23a1d0baee481237b69ea381
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.3.3-py3-none-any.whl

Download URL datahenge_cairn-0.3.3-py3-none-any.whl
Size 467.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
40901523f6e78db5443419f4c8a777dde0a0a3bd65273258af71a6f158561972
BLAKE2b-256 checksum
How to use checksums
700304fafca16023dcb617409c8dc39c778634a29298ddbdfb35dadb34fdd784
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

0.4.12

2 release files

0.4.11

2 release files

0.4.9

2 release files

0.4.8

2 release files

0.4.7

2 release files

0.4.6

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

This release

0.3.3 This release

2 release files

0.3.1

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page