# docs / 05

Publishing your own system

Anyone can publish a Linux system with chainboot. You need a root file system as a tar archive and one command.

Images are private by default. chainboot publish encrypts the system for your wallet alone, because what is written to a ledger can never be erased and a mistake must not expose anything. Publishing for everyone is a deliberate step: you add --public and a publisher key. This page explains the public case first, then private images.

The one command (public)

chainboot publish rootfs.tar.gz --name mylinux --version 1.0 --public --keypair me.json

chainboot reads the archive, packs and compresses it, shows what it will cost, and asks before sending anything. When it is done, the image is listed in your catalog and anyone can run it:

chainboot run <your-address>/mylinux

Step by step

1. A publisher key

Your publisher identity is a Solana keypair. Its address is your name on chain; images are found under it.

chainboot keygen me.json

This prints the address. Keep me.json private and make a backup: whoever has it can publish as you, and without it you cannot update your catalog.

On devnet, get free test SOL from https://faucet.solana.com for that address. On mainnet, send real SOL to it. See Networks and costs for how much.

2. A root file system

A root file system is "everything below /" of a Linux system, as a tar archive. The archive may be plain, gzip, xz or zstd. chainboot reads the whole archive before anything is sent and stops if it is not sound: an archive that was cut off (it must end the way tar archives end), an entry stored as a sparse file (make the archive without --sparse), or anything that would give an image no reader accepts. Here are four ways to get one.

From a distribution's download page. Many distributions publish ready archives:

curl -LO https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/alpine-minirootfs-3.24.4-x86_64.tar.gz
chainboot publish alpine-minirootfs-3.24.4-x86_64.tar.gz --name alpine --version 3.24.4 \
  --description "Alpine Linux 3.24" --source-url https://dl-cdn.alpinelinux.org/... --public --keypair me.json

From a machine you set up by hand. This is the easiest way to make something of your own:

chainboot run solinux-minimal --name mybox --no-autosave
#   ... install and configure what you need, then exit ...
chainboot export mybox mybox.tar
chainboot publish mybox.tar --name mybox --version 1.0 --reuse solinux-minimal --public --keypair me.json

From Debian's debootstrap:

sudo debootstrap --variant=minbase trixie ./rootfs
sudo tar -C rootfs -cf debian.tar .

From a Docker image:

docker export $(docker create debian:stable-slim) > debian.tar

3. Publish

chainboot publish debian.tar --name debian --version 13 --description "Debian 13, minimal" --public --keypair me.json
image         debian:13 (amd64)
digest        4c1f…
contents      6204 entries, 78.1 MB unpacked
to upload     19.8 MB in 1 pack(s)
transactions  about 5230
cost          about 0.0262 SOL on devnet
Publisher 7xQ… holds 1.0000 SOL
Publish for everyone? It can never be erased from the ledger. [y/N]

Add --dry-run to see these numbers without a keypair and without sending anything. The summary also has a line that states the visibility, and the question before a private upload reads "Publish privately now?".

Options:

OptionMeaning
--nameimage name: lower-case letters, digits, ., _, -
--versionversion text, for example 1.0 or 26.04.1
--archprocessor type of the system: amd64, arm64, riscv64, ... (default: this computer's)
--tagtags to publish under (repeatable). Default: the version and latest
--descriptionone line shown in chainboot list
--source-urlwhere the archive came from, recorded so others can check
--reuse <image>do not store files again that this image already has on chain (repeatable)
--cmd "<command>"what chainboot run starts instead of a login shell. The command is part of the image's identity (its digest): the same files with another command are another image
--publicpublish for everyone, unencrypted (without it the image is private)
--keypairwith --public: the publisher key that pays. It is always named, here or in the environment variable CHAINBOOT_KEYPAIR; chainboot never picks up a key from a usual place by itself
--dry-run, --yesonly show the cost / do not ask

If the upload is interrupted, run the same command again. It continues where it stopped and does not pay twice for what already landed.

Several processor types

Publish the same name and version once per architecture:

chainboot publish ubuntu-base-amd64.tar.gz --name mylinux --version 1.0 --arch amd64 --public --keypair me.json
chainboot publish ubuntu-base-arm64.tar.gz --name mylinux --version 1.0 --arch arm64 --public --keypair me.json

chainboot run picks the right one for each user.

Reusing what is already on chain

--reuse solinux-minimal tells chainboot to look at that image first. Every file of your system that is byte-for-byte identical to a file in it is referenced instead of uploaded. A system built on top of Solinux minimal that adds 30 MB of software costs 30 MB, not 120 MB.

The reused image can belong to any publisher. Readers check its packs by hash, so they do not have to trust that publisher; and before your upload starts, chainboot downloads the reused image and checks every file in it, so that nothing of yours is left out because that image merely claimed to have it.

Images for lazy machines

A lazy machine (Machines) fetches a file when it is first opened. For that to be quick, the image has to be stored differently: in many small packs instead of a few large ones, with files of one directory together.

chainboot publish rootfs.tar --name mydistro --version 1.0 --lazy

The image gets a little larger, because each small pack is compressed on its own (Solinux minimal: 23.3 MB instead of 21.6 MB; Solinux: 117 MB instead of 113 MB). It is the same image, with the same digest, and everybody can still run it the ordinary way.

Better still, tell chainboot which files a start of your system reads. They are then stored first, in packs of their own, and a lazy machine fetches those ahead instead of stumbling over them one by one:

chainboot run mydistro --lazy --name probe      # use it the way people will: a shell, the package manager, ...
chainboot stop probe
chainboot publish rootfs.tar --name mydistro --version 1.0 --lazy \
    --start-files ~/.chainboot/machines/probe/lazy-files.txt

A common arrangement is to publish both: the ordinary packing under the version and latest, and the lazy one under its own tag (--tag lazy).

Updating and removing

Publishing again under the same name and tag moves the tag to the new image. Machines that people already created keep using the image they were made from.

chainboot unlist mylinux:1.0 --keypair me.json     # remove one tag
chainboot unlist mylinux --keypair me.json         # remove the image from your catalog

Unlisting removes the entry from your catalog. The data stays on the ledger, and whoever knows the manifest's signature can still fetch it. Do not publish secrets.

Private images: for your wallet only

Without --public, an image is published for you alone:

chainboot login                                   # once: your wallet signs one message
chainboot fund 0.05                               # your vault pays for the upload
chainboot export mybox mybox.tar
chainboot publish mybox.tar --name mybox --version 1.0 --reuse solinux
chainboot run me/mybox                            # on any computer where you are signed in
chainboot list me                                 # your private images
chainboot unlist me/mybox

What "private" means exactly:

No --keypair is used: a private image is paid and signed by your vault, so the vault needs enough SOL (chainboot fund, chainboot whoami).

What it does not mean:

Machines made from a private image are saved and restored like all others; restoring one needs the same wallet, because the image itself has to be fetched again. A public image cannot reuse files of a private one (--reuse): that would publish their keys.

Lists: a catalog that several publishers keep together

A catalog is signed by one key. Everybody who runs images from it trusts whoever holds that key. A list spreads this over several keys. It names its publishers and how many of them must agree:

list:2:<address of A>,<address of B>,<address of C>        two of these three

An image is part of the list when that many of its publishers have the same image (the same content, compared by its digest) under the same name, tag and architecture in their own catalogs. Nothing extra is stored for a list; it is a way of reading catalogs that exist. More than half of the publishers must agree, so two different images can never both qualify, and one lost or stolen key can neither add an image nor replace one.

Using a list:

chainboot publisher add team list:2:9xQe...,4Zb7...,Hk3P...   # save it under a short name
chainboot list team                                           # what it contains, and who agreed
chainboot run team/solinux

Keeping a list, as one of its publishers:

# publisher A uploads the image as usual
chainboot publish rootfs.tar --name mydistro --version 1.0 --public --keypair a.json

# publishers B and C look at it, then add it to their own catalogs. Nothing is uploaded again.
chainboot pull 9xQe.../mydistro:1.0             # download and verify, inspect it as you see fit
chainboot endorse 9xQe.../mydistro:1.0 --keypair b.json
chainboot endorse 9xQe.../mydistro --keypair b.json           # the "latest" tag, separately

chainboot list team shows each publisher's catalog, the images enough of them agree on, and the ones still waiting for more publishers.

Updates need the same agreement. A tag that moves, such as latest, changes for the users of the list only when enough publishers have moved it to the same new image. While they disagree, the tag is held back, so move it in a set order, or keep one publisher more than the list needs. Tags that name a version do not move and are not affected.

chainboot publisher add <name> <address> also saves a single publisher under a short name.

What makes a good image

Showing your catalog to others

chainboot list <your-address>
chainboot run <your-address>/mylinux

In the web interface, "Other publisher" (in the Systems section) does the same; it takes an address, a saved name or a list.

Edit this page on GitHub