Container Building
This document assumes you already read
ContainerIntroduction.
Our tool to build images is
podman
together with a
Containerfile
describing the build.
Containerfile
The
Containerfile
contains all steps to create an image.
mkdir my-first-container
touch my-first-container/Containerfile
cd my-first-container
And then open the file in an editor of your choice.
FROM
Every Image needs a starting point. In theory we could start with
FROM scratch
With this we could tightly control what is part of the image and create very small images. For the time beeing we choose to start with something that is similiar to our "normal" server setup. Rocky8.
FROM library/rockylinux:8
This base image is about 220MB in size. For convenience a aco base image is available (
aco/base:8
). It is configured to use our in-house yum repositories and includes our certificate authority.
FROM aco/base:8
LABEL
Leave a note who is responsible for this
MAINTAINER my.name@gsi.de
COPY / ADD
copy in some local files
The source can be a path relative to the Containerfile (best practice) or an absolute path
COPY somefile /somewhereinthecontainer
COPY lsa-server.zip /tmp/lsa-server.zip
ADD has additional magic. It can handle urls and automatic tarball expansion. For clarity most of the time copy is the better solution.
RUN
Run a command inside the container.
RUN mkdir /opt/lsa-server
RUN cd /opt/lsa-server && unzip /tmp/lsa-server.zip
CMD
If the image is run as a container, this command should be executed
CMD /opt/lsa-server/bin/lsa-server.sh --config=DEV
ARG
Arguments can be passed to the build process and used during image building. For example it would be nice we could specify the version of our product without changing the containerfile
ARG version=default
RUN curl -o my-app.zip "https://example.com/my-app-${version}.zip"; unzip my-app.zip; rm -f my-app.zip
Note: Why is this in one line? See layers below.
Note: Couldn't we just use a pipe to extract on the fly? Zipfiles are not realy pipeable as the main header is at the content index is at the end of the file.
ENV
Environment variables are the main option to configure a container image during runtime.
ENV CONFIG dev
CMD /my/app.sh --config ${CONFIG}
what is the difference to an argument? We can specify the value during runtime
podman run -e CONFIG=pro lsa/server:version
USER
Even if containers are isolated, it is good practice to not run a process as root. The aco/base image includes a user alice which can be used for this. The instruction switches to the user. All commands after this are run as alice.
USER alice
CMD /my/app.sh
build
podman build -t lsa/server:version .
or if we have arguments
podman build -t lsa/server:13.0.1 --build-args version=13.0.1 .
if the base image (from) is present it is not pulled again. Images may follow a branch and have a stable tag. That is, their content changes, but the tag is stable. To update these images run
podman build --pull=always .
inspect
once the build completed we can inspect the image with all it's layers
podman inspect lsa/server:version
layers
An image consists of layers. Every layer is a readonly set of files. Each layer can add, remove, overwrite files in the lower layers. Layers are referenced via checksums and can be reused.
By default each ADD or RUN will create a new layer. If you add a 100MB file and then run rm to remove it again, this results in an intermediate layer with 100MB which is not required. To avoid this, we can skip creating layers.
podman build -t lsa/server:version --layers=false .
All steps are still visible but don't use space. During development it makes sense to have layers so as long as nothing changed previous steps could be retrieved from the layer cache, but before publishing it makes sense to reduce the size.
Note: if you installed additional packages using yum, wipe the package cache before shipping by cleaning up
RUN dnf install -y something
RUN dnf clean all
# or
RUN dnf install -y something; dnf clean all
# and best practice, install everything in one go, alphabetical sorted
RUN dnf install -y \
a \
b \
; dnf clean all
every image has a version tag. By default this is latest. If you want reproducibale builds never use latest in your from line. If you want to have a stable container, never use latest in your deployments.
publish
and finally we need to publish our image so everyone can use it. First we need to login to the
ContainerRegistry, see
Authentication how to get your clisecret.
podman login -u USERNAME -p CLISECRET registry.acc.gsi.de
so we can now push/deploy the image
podman push localhost/lsa/server:version registry.acc.gsi.de/lsa/server:version