S1: Other Options for Building Containers¶
Lesson Objectives
- See the full set of sections that a
deffile can contain, beyond the basics covered in Chapter 4. - Understand what the
%arguments,%setup,%files,%startscript, and%helpsections are for. - Know that a
deffile can be parameterised with build-time arguments and split into multiple build stages.
Questions
- What other sections can I include in a
deffile, and what does each one do? - How can I make a
deffile configurable when I build it?
In Chapter 4 we built a container using the most common def file sections: Bootstrap, From, %labels, %environment, %post, and %runscript. These are all you need for most containers. However, a def file can contain several other sections, and this supplementary page describes the full set so you know what is available.
The Sections of a def File¶
A def file can contain the sections below. You only need to include the ones relevant to your container — most containers use just a handful.
The header keywords sit at the very top of the file:
BootstrapandFrom: the base image the container is built on (see Chapter 4).Stage: names a build stage. This is used for multi-stage builds, where you build software in one stage and copy only the finished result into a smaller final stage (see Multi-Stage Builds below).
The remaining sections each begin with a % keyword:
%arguments: defines default values for template variables — the{{ ... }}placeholders used elsewhere in the file. For example,{{ VERSION }}is filled in from theVERSIONvalue set in%arguments. You can override these at build time with the--build-argoption.%setup: commands that run on the host during the build, before%post. The container's file system is available through the$APPTAINER_ROOTFSvariable, so you can create or place files into the container from the host. (Use this with care — these commands run on your own machine, not inside the container.)%files: copies files from the host into the container, written assource destination(we used this in Chapter 9 to copy programs into MPI containers).%environment: environment variables that are set every time the container runs (see Chapter 4). Note that these are not available during%post— they apply at runtime, not at build time.%post: commands run inside the container during the build to install and configure software (see Chapter 4).%runscript: the commands run when youapptainer runthe container (see Chapter 4).%startscript: the commands run when you start the container as a background service withapptainer instance start.%test: commands run at the end of the build (and whenever you runapptainer test) to check that the container was built correctly (see Chapter 8).%labels: metadata such as the author and version, which you can read back withapptainer inspect(see Chapter 4 and Chapter 7).%help: free text describing the container, shown when a user runsapptainer run-help(see S2: Other Commands in Apptainer).
A Note on %setup vs %post¶
Two of these sections sound similar but are very different, and it is worth being clear on the distinction:
%postruns inside the container — this is where you install software.%setupruns on the host — it can reach into the container's file system via$APPTAINER_ROOTFS, but the commands themselves execute on your machine.
Most of the time you want %post. Only use %setup when you specifically need to prepare files on the host before they go into the container.
Multi-Stage Builds¶
The Stage keyword lets you split a build into multiple stages. A common pattern is to compile software in a "build" stage (which needs compilers and development tools) and then copy only the finished program into a clean final stage. This keeps the final container small, because the compilers and intermediate build files are left behind. You refer to files from an earlier stage using %files from <stage-name>.
A Complete Example¶
The def file below uses every section, so you can see them all in one place:
Bootstrap: docker
From: ubuntu:{{ VERSION }}
Stage: build
%arguments
VERSION=24.04
%setup
touch /file1
touch ${APPTAINER_ROOTFS}/file2
%files
/file1
/file1 /opt
%environment
export LISTEN_PORT=54321
export LC_ALL=C
%post
apt-get update && apt-get install -y netcat
NOW=`date`
echo "export NOW=\"${NOW}\"" >> $APPTAINER_ENVIRONMENT
%runscript
echo "Container was created $NOW"
echo "Arguments received: $*"
exec echo "$@"
%startscript
nc -lp $LISTEN_PORT
%test
grep -q NAME=\"Ubuntu\" /etc/os-release
if [ $? -eq 0 ]; then
echo "Container base is Ubuntu as expected."
else
echo "Container base is not Ubuntu."
exit 1
fi
%labels
Author myuser@example.com
Version v0.0.1
%help
This is a demo container used to illustrate a def file that uses all
supported sections.
Note that this is a very complete def file, which makes it look overwhelming. In general you do not need to include all these features — most containers use only Bootstrap, From, %post, and %runscript. We show it here just to illustrate everything a def file can contain.
Keypoints
- A
deffile supports many sections; include only the ones your container needs. %setupruns on the host (with the container available at$APPTAINER_ROOTFS), while%postruns inside the container.%filescopies files in,%startscriptdefines a background service,%testvalidates the build, and%helpprovides documentation viaapptainer run-help.%argumentsand{{ ... }}templating let you parameterise a build, and theStagekeyword enables multi-stage builds that keep the final container small.