This page is intended to give an more in-depth guide for building/running a system container. If you are not familiar with the concept of system containers, please check out Container:System_Container#Useful_Links.
Files
This section describes the files necessary for the operation of a system container. A useful tool to check the correctness of the files, system container lint, can be found in the projectatomic repo on GitHub.
Dockerfile
A system container image can be built using docker from a Dockerfile.
If you are familiar with Dockerfile specifications, a more concise guide highlighting only the differences can be found at: Container:Guidelines#System_Containers
Example
FROM fedora:rawhide ENV VERSION=0.1 RELEASE=1 ARCH=x86_64 LABEL name="$FGC/mycontainer" \ version="$VERSION" \ release="$RELEASE.$DISTTAG" \ architecture="$ARCH" \ summary="My test system container" \ maintainer="Me <me@me.com>" \ atomic.type='system' RUN dnf -y --setopt=tsflags=nodocs install nmap-ncat && dnf clean all COPY run.sh /usr/bin/ COPY tmpfiles.template service.template manifest.json \ config.json.template /exports/
Notes
- Start by pinning the image to a specific version of the OS. This is important not only because system containers provide OS-specific services, but also to reduce local storage space required for multiple images. Ideally system container images for a specific OS/version will be from the same base image. That base image will be stored as a layer in OSTree, and all future containers will share that same layer as a base image. Thus an extra container image will only take up storage space necessary for extra files and packages specific to that container image, greatly reducing the necessary storage space to run multiple containers.
- Label information follow mostly the same Container:Guidelines#LABELS. The only special label for system containers is the atomic.type label. If this label exists and is specified to be
system
, it tells the atomic command line that this image is for a system container ONLY. By default it will be pulled to OSTree on your host instead. Do not specify this if you intend the container to work as other types of containers as well. - Install necessary packages, note to
--setopt=tsflags=nodocs
anddnf clean all
to reduce image size. - Copy in necessary files (preferred over add as highlighted by docker best practices. Scripts used for setup/running the container should be added to
/usr/bin/
, and other files should be copied to/exports/
. There are 4 files that the atomic command line recognizes as "special files" for system container runtime. These are:config.json.template manifest.json service.template tmpfiles.template
. These must be inexports
and their usage is highlighted below.
config.json.template
This file is a runc runtime config file, the exact details of which can be found at [their github page]. Essentially this file contains metadata necessary for operation of the container. This includes the process to run, environment variables to inject, etc.
This file is NOT mandatory. If you wish to run the container using runc as the runtime, it is highly recommended you include your own. If not included in the image under /exports/config.json.template
, a default one will be generated during install time. Alternatively, the container can run just as a systemd service, in which case all the image needs is a service.template file to dictate the systemd service.
Example
{ "ociVersion": "1.0.0", "platform": { "os": "linux", "arch": "amd64" }, "process": { "terminal": false, "user": { "uid": 0, "gid": 0 }, "args": [ "/usr/bin/run.sh" ], "env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm", "NAME=$NAME" ], "cwd": "/", "capabilities": [ "CAP_CHOWN", "CAP_FOWNER", ... ], "noNewPrivileges": false }, "root": { "path": "rootfs", "readonly": true }, "mounts": [ { "source": "/dev", "destination": "/dev", "type": "bind", "options": [ "rbind", "shared", "rw", "mode=755" ] }, { "source": "/sys", "destination": "/sys", "type": "bind", "options": [ "rbind", "shared", "rw", "mode=755" ] }, ... } ], "hooks": {}, "linux": { "resources": { "devices": [ { "allow": true, "access": "rwm" } ] }, "namespaces": [ { "type": "mount" } ... ] } }
Notes
process['env']
is used to declare all ell environment variables to be used in the container.root['path']
must be "rootfs". The container will be checked-out at/var/lib/containers/atomic/$NAME
, and the rootfs will be/var/lib/containers/atomic/$NAME/rootfs.
root['readonly']
must be true. As mentioned above all containers with the same base image share that image. Essentially files in the checked-out container are hardlinks to the files in ostree storage. Thus the rootfs should not be modified.process['terminal']
must be false.process['args']
are the executables that will be run when the container is started. This can be a script you added to/usr/bin
, or an existing binary.mounts
are mounts to be used at runtime. This essentially is used to bind locations in the container to the locations on the host.
manifest.json
This is an OPTIONAL file that declare the default value of environment variables declared in process['env']
.
Example
{ "version": "1.0", "defaultValues": { "VAR_1": "value1", "VAR_2": "value2", ... } }
Notes
All variables declared in process['env']
MUST be given a default value here (can be "" for blank). There are some exceptions:
* DESTDIR * NAME * EXEC_START * EXEC_STOP * HOST_UID * HOST_GID * RUN_DIRECTORY* * STATE_DIRECTORY* * UUID*
These variables are given default values. The ones denoted with * can be overridden with the --set
flag during install. The others have a value set by the host OS and cannot be overridden.
service.template
This is the systemd service unit template file. During installation it will be converted to $CONTAINER_NAME.service and added to the host. This is used to start/stop the container (as a systemd service). Details can be found at the systemd service man page.
Example
[Unit] Description=Useful Service After=network.target [Service] ExecStart=$EXEC_START ExecStop=$EXEC_STOP Restart=on-failure WorkingDirectory=$DESTDIR RuntimeDirectory=${NAME} [Install] WantedBy=multi-user.target
Notes
- At the very minimum ExecStart ExecStop and WorkingDirectory should be defined.
- EXEC_START and EXEC_STOP are auto-generated to start and stop the service. They are respectively
runc start $SERVICE_NAME
andrunc kill $SERVICE_NAME
.runc start
will in turn call theprocess['args']
defined inconfig.json.template
. These variables cannot be overridden. - If you do not wish to run the container with runc, do not use the above 2 variables. Instead directly use the command to start the service.
- WorkingDirectory ($DESTDIR) is the container checkout location.
tmpfiles.template
This is an OPTIONAL file that creates systemd tempfiles. For more info please refer to the manpage.
Example
d ${STATE_DIRECTORY}/etcd/${NAME}.etcd 0700 ${HOST_UID} ${HOST_GID} - - Z ${STATE_DIRECTORY}/etcd/${NAME}.etcd 0700 ${HOST_UID} ${HOST_GID} - - d ${RUN_DIRECTORY}/${NAME} - - - - -
Building the Container Image
Once the above files are ready, they should be put into the same directory. Then you can build the image through docker with docker build -t $CONTAINER_NAME .
.
Operation of a System Container
Let's say we want to create an awesome.service
with the above files as a system container; the following steps will guide you through the process. System containers are managed by the atomic command line.
Pulling the Image
The images for system containers are stored in the local OSTree. The image can be acquired through the following methods:
- Pulling from a registry:
atomic pull --storage ostree $REGISTRY/awesome will pull the image directly from the registry to local OSTree. The pull uses skopeo.
- Pulling from the local docker (e.g. if you built with the above process):
atomic pull --storage ostree docker:awesome
. Thedocker:
prefix tells the atomic command line to pull from the local docker. - Creating the image from a dockertar:
atomic pull --storage ostree dockertar:awesome
Once the image is pulled to the local storage, you can view information of the image (Dockerfile labels and environment variable info) with atomic images info awesome
.
Install/Uninstall a Container
The container is installed with atomic install --system awesome
. This will perform a checkout of the image to /var/lib/containers/awesome.0
,
create /var/lib/containers/atomic/awesome
as a link to the ".0" checkout, and create/enable the systemd service.
- If you wish to set values for environment variables, you can add a
--set $VAR=$VALUE
flag.
- If you wish to use an alternative location as the rootfs (e.g. through NFS), you can use the
--rootfs=$LOCALTION_OF_ROOTFS
flag.
The command atomic uninstall awesome
will uninstall the container and remove the service plus tempfiles.
Runtime
Once the container is installed, it can be started with systemctl start awesome.service
, and stopped through systemctl stop awesome.service
.
You can view active containers with atomic containers list
.
If there is a new version of the image you wish to use, you can pull the new version to OSTree and update the container with atomic containers update awesome
. This will stop the service, create a new checkout at /var/lib/containers/atomic/awesome.1
(or .0 again if the container has been updated previously, as only 2 version are preserved), and restart the service with the new image. The environment variables set during the previous checkout will be preserved, but you can override them with --set VAR=VALUE
flag during the update.
You can go back to the previous version with atomic containers rollback awesome
, which performs the reverse of an update and goes to the previous deployment.
Useful Links
- Container:Guidelines For general container guidelines.
- The Official Blog Post for more general information.
- The GitHub repo for existing files
Discussion
For suggestions, feedback, or to report issues with this page please contact the Fedora Cloud SIG.