How to build derived bootc container images
The original Docker container model of using "layers" to model applications has been extremely successful. This project aims to apply the same technique for bootable host systems - using standard OCI/Docker containers as a transport and delivery format for base operating system updates.
Lifecycle binding code and configuration
At the current time, the role of bootc is solely to boot and
upgrade from a single container image. This is a very simplistic
model, but it is one that captures many use cases.
In particular, the default assumption is that code and configuration for the base OS are tightly bound. Systems which update one or the other asynchronously often lead to problems with skew.
Containerized vs 1:1 host:app
A webserver is the classic case of something that can be run as a container on a generic host alongside other workloads. However, many systems today still follow a "1:1" model between application and a virtual machine. Migrating to a container build for this can be an important stepping stone into eventually lifting the workload into an application container itself.
Additionally in practice, even some containerized workloads have such strong bindings/requirememnts for the host system that they effectively require a 1:1 binding. Production databases often fall into this class.
httpd
Nevertheless, here’s a classic static http webserver example;
an illustrative aspect is that we move content from /var into /usr.
FROM quay.io/centos-bootc/centos-bootc:stream9
# The default package drops content in /var/www, and on bootc systems
# we have /var as a machine-local mount by default. Because this content
# should be read-only (at runtime) and versioned with the container image,
# we move it to /usr/share/www instead.
RUN dnf -y install httpd && \
systemctl enable httpd && \
mv /var/www /usr/share/www && \
sed -ie 's,/var/www,/usr/share/www,' /etc/httpd/conf/httpd.conf
# Further, we also disable the default index.html which includes the operating
# system information (bad idea from a fingerprinting perspective), and crucially
# we inject our own content as part of the container image build.
# This is a key point: In this model, the webserver content is lifecycled exactly
# with the container image build, and you can change it "day 2" by updating
# the image. The content is underneath the /usr readonly bind mount - it
# should not be mutated per machine.
RUN rm /usr/share/httpd/noindex -rf
COPY index.html /usr/share/www/html
EXPOSE 80
Injecting a root SSH public key into container builds
See examples/included-ssh-pubkey for the canonical reference. Here is a copy for convenience:
FROM <baseimage>
# You *must* specify this argument; it is a SSH public key
# (in general an authorized_keys formatted data)
# that will be used for the `root` user by default.
ARG SSHPUBKEY
# In this example, we add /usr/ssh to the search path OpenSSH uses for keys.
# The rationale for this is that `/usr` is always part of the immutable
# container state, as opposed to user home directories which are mutable.
# In this pattern, you can always have a "fallback" key available, but
# e.g. use an external system (such as cloud-init) to live-update
# the traditional authorized_keys in the user's home directories.
RUN set -eu; mkdir -p /usr/ssh && \
echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \
echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys
Provide your public SSH key as a build argument:
$ podman build --build-arg=SSHPUBKEY=$HOME/.ssh/id_rsa.pub .
General configuration guidance
See the bootc upstream guidance.
Many configuration changes to a Linux system boil down effectively to
writing configuration files into /etc or /usr - those operations
translate seamlessly into booted hosts via a COPY instruction
or similar in a container build.
More examples
See Examples for many examples of container image definitions!
Want to help? Learn how to contribute to Fedora Docs ›