Stacker is a tool that allows for building OCI images in a reproducible manner, completely unprivileged. For this tutorial, we assume you have followed the installation guide and your environment satisfies all the runtime dependecies.
The basic input to stacker is the
stacker.yaml file, which describes what the
base for your OCI image should be, and what to do to construct it. One of the
smallest stacker files is just:
first: from: type: docker url: docker://centos:latest
Note the key
first represents the name of the layer, and it can have any value except
config, which has a special usage, see the stacker yaml documentation
With this stacker file as
first.yaml, we can do a basic stacker build:
$ stacker build -f first.yaml building image first... importing files... Getting image source signatures Copying blob sha256:5e35d10a3ebadf9d6ab606ce72e1e77f8646b2e2ff8dd3a60d4401c3e3a76f31 69.60 MB / 69.60 MB [=====================================================] 16s Copying config sha256:44a17ce607dadfb71de41d82c75d756c2bca4db677bba99969f28de726e4411e 862 B / 862 B [============================================================] 0s Writing manifest to image destination Storing signatures unpacking to /home/ubuntu/tutorial/roots/_working running commands... generating layer... filesystem first built successfully
What happened here is that stacker downloaded the
centos:latest tag from the
docker hub and generated it as an OCI image with tag "first". We can verify
$ umoci ls --layout oci centos-latest first
The centos-latest there is the OCI tag for the base image, and first is the image we generated.
The next thing to note is that if we do another rebuild, less things happen:
$ stacker build -f first.yaml building image first... importing files... found cached layer first
Stacker will cache all of the inputs to stacker files, and only rebuild when
one of them changes. The cache (and all of stacker's metadata) live in the
.stacker directory where you run stacker from. Stacker's metadata can be cleaned with
stacker clean, and its entire cache can be removed with
stacker clean --all.
So far, the only input is a base image, but what about if we want to import a script to run or a config file? Consider the next example:
first: from: type: docker url: docker://centos:latest import: - config.json - install.sh run: | mkdir -p /etc/myapp cp /stacker/config.json /etc/myapp/ /stacker/install.sh
If the content of
install.sh is just
echo hello world, then stacker's
output will look something like:
$ stacker build -f first.yaml building image first... importing files... copying config.json copying install.sh Getting image source signatures Skipping fetch of repeat blob sha256:5e35d10a3ebadf9d6ab606ce72e1e77f8646b2e2ff8dd3a60d4401c3e3a76f31 Copying config sha256:44a17ce607dadfb71de41d82c75d756c2bca4db677bba99969f28de726e4411e 862 B / 862 B [============================================================] 0s Writing manifest to image destination Storing signatures unpacking to /home/ubuntu/tutorial/roots/_working running commands... running commands for first + mkdir -p /etc/myapp + cp /stacker/config.json /etc/myapp + /stacker/install.sh hello world generating layer... filesystem first built successfully
There are two new stacker file directives here:
import: - config.json - install.sh
Which imports those two files into the
/stacker directory inside the image.
This directory will not be present during the final image, so copy any files
you need out of it into their final place in the image. Also, importing things
from the web (via http://example.com/foo.tar.gz urls) is supported, and these
things will be cached on disk. Stacker will not evaluate as long as it has a
file there, so if something at the URL changes, you need to run
--no-cache argument, or simply delete the file from
And then there is:
run: | mkdir -p /etc/myapp cp /stacker/config.json /etc/myapp/ /stacker/install.sh
Which is the set of commands to run in order to install and configure the image.
Also note that it used a cached version of the base layer, but then re-built the part where you asked for commands to be run, since that is new.
Finally, stacker offers "build only" containers, which are just built, but not emitted in the final OCI image. For example:
build: from: type: docker url: docker://ubuntu:latest run: | apt update apt install -y software-properties-common git apt-add-repository -y ppa:gophers/archive apt update apt install -y golang-1.9 export PATH=$PATH:/usr/lib/go-1.9/bin export GOPATH=~/go mkdir -p $GOPATH/src/github.com/openSUSE cd $GOPATH/src/github.com/openSUSE git clone https://github.com/openSUSE/umoci cd umoci make umoci.static cp umoci.static / build_only: true umoci: from: type: docker url: docker://centos:latest import: stacker://build/umoci.static run: cp /stacker/umoci.static /usr/bin/umoci
Will build a static version of umoci in an ubuntu container, but the final
image will only contain an
umoci tag with a statically linked version of
/usr/bin/umoci. There are a few new directives to support this:
indicates that the container shouldn't be emitted in the final image, because we're going to import something from it and don't need the rest of it. The line:
is what actually does this import, and it says "from a previously built stacker image called 'build', import /umoci.static".
The from:scratch containers can be confusing to use at first, because the "run" section runs a shell and commands inside the container, but there are no commands or shell in the container. Below is an example of how to create a minimal layer using from:scratch
build: from: type: docker url: docker://ubuntu run: | cp /etc/hosts / apt-get update apt-get -y install busybox-static cp $(which busybox) / build_only: true bare: from: type: scratch import: - stacker://build/hosts - stacker://build/busybox run: | #!/stacker/busybox sh export PATH=/stacker:$PATH busybox cp /stacker/hosts /
Note that the resulting container does not have only a single file. The running the run: section in a container left a few more artifacts:
bare/rootfs bare/rootfs/dev bare/rootfs/etc bare/rootfs/etc/resolv.conf bare/rootfs/hosts bare/rootfs/proc bare/rootfs/sys
But no shell or binaries and no full distribution.