stacker: build OCI images without host privilege
Ahoy! Recently, I've been working on a tool called stacker, which allows unprivileged users to build OCI images. The images that are generated are generated without uid shifting, so they look like any other OCI image that was generated by Docker or some other mechanism, while not requiring root (worth noting that this is what James Bottomley has described as his motivation for writing shiftfs).
Some base setup is required in order to make this happen, though. First, you can follow stacker's install guide to build and install it.
Next, as with any user namespaces setup, stacker needs a 65k uid delegation. On my ubuntu VM with the ubuntu user, this looks like,
$ grep ubuntu /etc/subuid
ubuntu:165536:65536
$ grep ubuntu /etc/subgid
ubuntu:165536:65536
Note that these can be any 65k range of subuids, stacker will use whatever you give the user you run it as.
Finally, stacker also needs a btrfs filesystem. Stacker was designed to build a large number of varying images from a single base image, and uses btrfs to avoid doing a large amount of i/o (and compression/decompression), undiffing filesystems back to their original state. For the purposes of this blog post, we can just use a loopback mounted btrfs filesystem. A slightly modified excerpt from the stacker test suite:
# btrfs setup
sudo truncate -s 100G btrfs.loop
sudo mkfs.btrfs btrfs.loop
sudo mkdir -p roots
# allow for unprivileged subvolume deletion; use a sane flushing strategy
sudo mount -o user_subvol_rm_allowed,flushoncommit,loop .stacker/btrfs.loop roots
# now make sure ubuntu can actually do stuff with this filesystem
sudo chown -R ubuntu:ubuntu roots
And with that, we can actually run stacker and build an image:
stacker build -f ./stacker.yaml
What goes in stacker.yaml
you ask? Consider the example from stacker's readme:
centos:
from:
type: tar
url: http://example.com/centos.tar.gz
environment:
http_proxy: http://example.com:8080
https_proxy: https://example.com:8080
labels:
foo: bar
bar: baz
boot:
from:
type: built
tag: centos
run: |
yum install openssh-server
echo meshuggah rocks
web:
from:
type: built
tag: centos
import: ./lighttp.cfg
run: |
yum install lighttpd
cp /stacker/lighttp.cfg /etc/lighttpd/lighttp.cfg
entrypoint: lighthttpd
volumes:
- /data/db
working_dir: /var/lib/www
The top level describes the name of a tag in the OCI image to be built, in this case there will be three tags at the end: centos
, boot
, and web
(notably, this example is quite contrived :). Underneath those, there are the following keys:
. from
: this describes the base image that stacker will start from. You can
either start from some other image in the same stackerfile, a Docker image, or a tarball.
. import
: A set of files to download or copy into the container. Stacker
will put these files at /stacker
, which will be automatically cleaned up after the commands in the run
section are run and the image is finalized.
. run
: This is the set of commands to run in order to build the image; they
are run in a user namespaced container, with the set of files imported available in /stacker
.
. environment
, labels
, working_dir
, volumes
: these all correspond
exactly to the similarly named bits in the OCI image config spec, and are available for users to pass things through to the runtime environment of the image.
That's a bit about stacker. Hopefully some more details about the internals will appear at some point :). Happy hacking!