Skip to main content

Custom Container Images

  • Creating Images with Containerfiles
    • A Containerfile lists a set of instructions that a container runtime uses to build a container image.
    • We can use the image to create any number of containers.
    • Each instruction causes a change that is captured in a resulting image layer.
    • These layers are stacked together to form the resulting container image.
  • Choosing a Base Image
    • When we build an image, podman executes the instructions in the Containerfile and applies the changes on top of a container base image. 
    • A base image is the image from which your Containerfile and its resulting image is built.
    • The base image you choose determines the Linux distribution and its components, such as:
      • Package manager
      • Init system
      • Filesystem layout
      • Preinstalled dependencies and runtimes
    • The base image can also influence other factors, such as image size, vendor support, and processor compatibility.
    • Red Hat provides container images intended as a common starting point for containers known as universal base images (UBI).
      • These UBIs come in four variants: standard, init, minimal, and micro.
        • Standard
          • This is the primary UBI, which includes DNF, systemd, and utilities such as gzip and tar.
        • Init
          • Simplifies running multiple applications within a single container by managing them with systemd.
        • Minimal
          • This image is smaller than the init image.
          • This image uses the microdnf minimal package manager instead of the full-sized version of DNF.
        • Micro
          • This is the smallest available UBI because it only includes the bare minimum number of packages.
          • This image does not include a package manager.
      • Red Hat also provides UBI-based images that include popular runtimes, such as Python and Node.js.
      • These UBIs use Red Hat Enterprise Linux (RHEL) at their core and are available from the Red Hat Container Catalog. 
  • Containerfile Instructions
    • Containerfiles use a small domain-specific language (DSL) consisting of basic instructions for crafting container images.
      • FROM
        • Sets the base image for the resulting container image. Takes the name of the base image as an argument.
      • WORKDIR
        • Sets the current working directory within the container. Instructions that follow the WORKDIR instruction run within this directory.
      • COPY
        • Copy files from the build host into the file system of the resulting container image.
        • Relative paths use the host current working directory, known as the build context.
        • Both instructions use the working directory within the container as defined by the WORKDIR instruction.
      • ADD
        • Copies files from URLs.
        • Unpacking tar archives in the destination image.
        • Since ADD instruction adds functionality that might not be obvious, prefer the COPY instruction for copying local files into the container image.
      • RUN
        • Runs a command in the container and commits the resulting state of the container to a new layer within the image.
      • ENTRYPOINT
        • Sets the executable to run when the container is started.
        • The ENTRYPOINT instruction defines an executable, or command, that is always part of the container execution.
          • For this additional arguments are passed to the provided command.
        • Since we cannot override Entrypoint instruction directly at runtime of container as in case of CMD 
          • We can overwrite the entrypoint by using the podman run --entrypoint command 
      • CMD
        • Runs a command when the container is started. This command is passed to the executable defined by ENTRYPOINT.
        • Base images define a default ENTRYPOINT, which is usually a shell executable, such as Bash.
        • The ENTRYPOINT and CMD instructions specify the command to execute when the container starts. 
        • A valid Containerfile must have at least one of these instructions.
        • If we use the CMD instruction, then passing arguments to the container overrides the command provided in the CMD instruction.
        • When a Containerfile specifies both ENTRYPOINT and CMD then CMD changes its behavior.
        • The values provided to CMD are passed as default arguments to the ENTRYPOINT.
        • The ENTRYPOINT and CMD instructions have two formats for executing commands
          • Text array
            • ENTRYPOINT ["executable", "param1", ... "paramN"]
          • String form
            • CMD executable param1 ... paramN
            • The string form wraps the executable in a shell command such as sh -c "executable param1 … paramN".
      • USER
        • Changes the active user within the container.
        • Instructions that follow the USER instruction run as this user.
        • We should use a different user other than root for security reasons.
      • LABEL
        • Adds a key-value pair to the metadata of the image for organization and image selection.
      • EXPOSE
        • Adds a port to the image metadata indicating that an application within the container binds to this port.
      • ENV
        • Defines environment variables that are available in the container.
        • We can declare multiple ENV instructions within the Containerfile. 
        • We can use the env command inside the container to view each of the environment variables.
        • The ENV instruction lets you specify environment dependent configuration, for example,hostnames, ports or usernames. 
        • The containerized application can use the environment variables at runtime.
        • To include an environment variable, use the key=value format.
      • ARG
        • Defines build-time variables, typically to make a customizable container build.
        • We configure the ENV instructions by using the ARG instruction.
        • This preserves the build-time variables for runtime.
        • Use the ARG instruction to define build-time variables, typically to make a customizable container build.
          • For example hosting envirnoment etc
        • We can configure a default build-time variable value if the developer does not provide it at build time.
        • While building the container image, use the --build-arg flag to set the value
          • podman build --build-arg key=example-value
        • If we do not provide the --build-arg flag, then Podman uses the default values during the build process. 
        • We can change the values during the build process without changing the Containerfile by using the ARG instruction.
        • Mostly ENV instructions are configured using the ARG instruction.
          • Thus preserving the build-time variables for runtime.
      • VOLUME
        • Defines where to store data outside of the container. 
        • It configures the path where Podman mounts persistent volume inside of the container.
        • We can define more than one path to create multiple volumes.
        • Use the VOLUME instruction to persistently store data.
          • The value is the path where Podman mounts a persistent volume inside of the container.
        • The VOLUME instruction accepts more than one path to create multiple volumes.
        • When we inspect an image the Mountpoint field gives us the absolute path to the directory where the volume exists on our host file system.
        • Volumes created from the VOLUME instruction have a random ID in the Name field and are considered Anonymous volumes.
        • To remove unused volumes, use the podman volume prune command.
        • To create a named volume by using the podman use volume create command.
        • Use podman volume ls command to include the Mountpoint field of every volume.
    • Each Containerfile instruction runs in an independent container by using an intermediate image built from every previous command.
    • Each instruction is independent from other instructions in the Containerfile.
    • We can specify an image name to identify an image.
      • The image name is a string composed of letters, numbers, and some special characters.
      • An image tag comes after the image name and is delimited by a colon (:).
      • If we omit an image tag, podman uses the default latest tag.
    • The full name of the image includes both a name and an optional image tag.
    • Reducing the number of RUN instructions also reduces the number of resulting image layers.
      • Merge all RUN instrusctions if possible.
    • By using WORKDIR we don't need to change directory in other instructions.
  • Advanced Containerfile Instructions
    • The container must not be bound to a specific environment i.e. it should not require rebuild before deploying to a production environment.
    • The container should not contain developer tools, such as a debugger, text editors, or compilers.
    • The container should not contain build-time dependencies that are not necessary at runtime.
    • The container should not generate a large volume of files stored on the copy-on-write file system, this limits the performance of the application.
    • We can reduce image storage footprint by using the multistage container build pattern.
    • We should customize container runtime with environment variables.
    • We should use volumes to decrease the container size and increase the performance of writing files.
  • Podman Secrets
    • A secret is a blob of sensitive information required by containers at runtime.
      • This can be usernames, passwords, or keys.
      • For example credentials for connecting to a database etc. 
    • After creating the secret, we must instruct the application container to make the credentials available to the application when it starts. 
      • We use the podman secret subcommands for this.
    • We can create secrets by using either a file, or by passing the sensitive information to the standard input (STDIN).
    • To create a secret from a file, run the podman secret create subcommand specifying the name of the file containing the sensitive information, and the name of the secret to create as arguments. 
      • The output of the podman secret create subcommand displays the secret ID.
    • The podman secret create command supports different drivers to store the secrets.
      • We use the --driver or -d option to specify one of the supported secret drivers
        • file(default)
          • Stores the secret in a read-protected file.
        • pass
          • Stores the secret in a GPG-encrypted file.
        • shell
          • Manages the secret storage by using a custom script
    • We can remove a secret by running the podman secret rm command, and providing the name of the secret as argument.
  • Running Containers With Podman Secrets
    • To make secrets available for use to a container, execute the podman run command with the --secret option, and specify the name of the secret as parameter.
      • Use the --secret option multiple times for multiple secrets.
    • When we use secrets, Podman retrieves the secret and places it on a tmpfs volume and then mounts the volume inside the container in the /run/secret directory as a file based on the name of the secret.
    • To prevent secrets from being stored in an image, neither the podman commit nor podman export commands copy the secret data to an image or .tar file.
  • Multistage Builds
    • A multistage build uses multiple FROM instructions to create multiple independent container build processes, also called stages.
    • Every stage can use a different base image and you can copy files between stages.
    • The resulting container image consists of the last stage.
    • Multistage builds can reduce the image size by only keeping the necessary runtime dependencies.
    • For example if we are building a container with nodejs application we will follow following steps.
      • The npm install command will install the required NPM packages, which includes packages that are only needed at build-time.
      • The npm run build command uses the packages to create an optimized production-ready application build.
      • Then, the container uses an HTTP server to expose the application by using the serve command.
      • The above image contains both the build-time and runtime dependencies, which increases the image size. 
      • The resulting image also contains the Node.js runtime, which is not used at container runtime and may increase the attack surface of the container.
    • To avoid above issue, define two stages:
      • First stage: Build the application.
      • Second stage: Copy and serve the static files by using an HTTP server, such as NGINX or Apache Server.
  • Container Data Layers
    • Container images use a copy-on-write (COW), layered file system. When you create a Containerfile, the RUN, COPY, and ADD instructions create layers.
    • The layered COW file system ensures that a container remains immutable.
    • On starting a container, Podman creates and mounts a thin, ephemeral, writable layer on top of the container image layers.
    • On deleting the container, Podman deletes the writable thin layer.
      • All container layers stay identical, except for the thin writable layer.
  • Cache Image Layers
    • Because all container layers are identical, multiple containers can share the layers.
    • Podman caches immutable layers, and builds only those layers that are mutable or not cached.
    • Caching decreases the build time.
    • We can separate build and src file directories
      • After copying build packaging files and Running the Compile and Install of dependencies.
      • Copy the src folder separately.
      • Since each time we run the RUN command a separte immutable layer is created.
      • Thus if we change your application source code in the src directory and rebuild container image, then the dependency layer is cached and skipped, which reduces the build time.
  • Reduce Image Layers
    • Reduce the number of container image layers by chaining RUN instructions.
    • We can chain commands by using the double ampersand (&&).
    • We can also use the backslash character (\) to break a long command into multiple lines.
    • Advantage of chaining commands is that it creates less container image layers, which typically results in smaller images.
      • Chained commands are more difficult to debug and cache.
    • We can also configure Podman to squash the layers.
      • Use the --squash option to squash layers declared in the Containerfile.
      • Use the --squash-all option to also squash the layers from the parent image.
    • We can reduce the number of layers by using multistage builds in combination with chaining some commands.
  • Refereces

Comments