Crafting Superior Container Images: A Comprehensive Guide

Posted on August 17, 2023

Introduction

In the current era of technology, many applications are either being built from scratch or being modernized from monolithic architectures using microservice architectures. 

Containerization brings a host of benefits to the table. It enables portability, allowing you to deploy containers across various infrastructures. It also ensures fault isolation, with different containers operating as isolated processes within their own user space in the host OS. This isolation means a crash or failure in one container doesn’t affect others. Furthermore, containerization simplifies the management of deployment and version control.

As containerized applications continue to grow, ensuring that your images are optimized, secure, and reliable is crucial. More than ever, containers play an essential role in building and deploying microservices, where providing the correct application versions and required dependencies can be complex. This article will discuss best practices for building superior container images for use on various services.

Building Superior Images

We will explore various approaches to build superior images. While this isn’t an exhaustive list, these topics provide a solid foundation for your image builds, and you can adopt them as you wish.

Use Trusted Images and Source

Using a base image from a trusted source can enhance the security and reliability of your container. This security enhancement is because you can be confident that the base image has been thoroughly tested and vetted. It can also reduce the burden of establishing provenance, as you only need to consider the packages and libraries you include in your image rather than the entire base image.

Here are some steps and examples of how to do this:

  1. Use Official Images: When using a base image for your Dockerfile, prefer official images from trusted sources. For example, use Python’s official image from Docker Hub or another trusted registry. The community or organizations behind these technologies maintain and vet these images. Example:
    FROM python:3.8-slim-buster
  1. Use Images from Trusted Registries: Use images from trusted container registries like Docker Hub, Google Container Registry (GCR), or Amazon Elastic Container Registry (ECR). These registries often provide security scanning features to help identify known image vulnerabilities.
  2. Verify Image Checksums: When pulling an image, you can verify its SHA256 checksum to ensure the image hasn’t been tampered with. Docker does this automatically when pulling images, but you can also manually check the image ID against the expected SHA256 checksum.
  3. Regularly Update Base Images: Update your base images to include the latest security patches. This update can be done manually or using automation tools like Dependabot, Renovate, or Keel. It is important to keep images up-to-date by regularly updating the latest secure versions of the software and libraries included in the image. As new versions of the images get created, they should be explicitly tagged with versions such as v1, v2, rc_12072023, etc., instead of tagging as the latest. Using explicit tags instead of the latest tag could prevent situations where the image with the latest tag isn’t updated and instead gives a false appearance that the image contains the latest version of the application.

Secure Your Secrets

Secrets management involves securely storing and managing sensitive information, like passwords and encryption keys, and externalizing environment-specific application configuration. You must not keep secrets in an image. Instead, use an external service such as AWS Secrets Manager to store and manage them.

Securing your secrets is critical to managing and deploying applications, especially in containerized applications. Secrets can include sensitive data such as API keys, database credentials, tokens, or encryption keys that your application needs to interact with other services. Here’s how you can secure your secrets:

  1. Never Store Secrets in Images or Code: You must never include secrets in the container image or the application code. Including a secret in an image means that anyone who pulls the image can retrieve the secret. Similarly, hard-coding a secret into your application allows anyone accessing the application code to retrieve the secret.
  2. Use Environment Variables: One standard method for providing secrets to a container at runtime is through environment variables. This method keeps the secrets out of the image. However, it’s important to note that anyone who can run the `docker inspect` command against the running container can see these environment variables. Hence, this method is unsuitable for highly sensitive secrets.
  3. Use Docker Secrets or Kubernetes Secrets: If you’re using Docker Swarm or Kubernetes, you can use Docker Secrets or Kubernetes Secrets. These services allow you to store and manage sensitive information, such as passwords, OAuth tokens, and ssh keys.
  4. Use a Secrets Manager: A more secure and scalable way to handle secrets is to use a secrets management service like AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault. These services store secrets securely and provide them to your application at runtime. They also allow you to rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle.

Reduce the Attack Surface

Images, by default, are not secure and can potentially be vulnerable to attacks. Several approaches can help mitigate the attack surface, such as building from scratch, removing unwanted packages, using a minimal base image, and securing image configuration. 

Here are some steps and examples of how to do this:

  1. Use Minimal Base Images: Use minimal base images that include only what your application needs to run. This approach reduces the attack surface by limiting the number of components that could contain vulnerabilities. For example, consider using Alpine Linux, a security-oriented, lightweight Linux distribution.
  2. Scan Images for Vulnerabilities: Regularly scan your images for known vulnerabilities using tools like Clair and Anchore or Snyk (which we use and love) or the scanning features provided by container registries like Docker Hub, Amazon ECR, or Google Container Registry.
  3. Run as a Non-Root User: By default, Docker containers run as the root user, which has the highest level of system privileges—running a container as root can expose it to potential security risks. If an attacker gains access to a container running as root, they would have complete control over it, potentially compromising the host machine or other containers. 

To mitigate this risk, it’s advisable to run processes inside the container as a non-root user. This approach ensures that the attacker would have limited capabilities within the container, even if there’s a security breach.

  Here’s how you can set a non-root user in a Dockerfile:

  FROM some-base-image

  # Create a new user with ID 1000 and set it as the default user
  RUN adduser --uid 1000 --disabled-password --gecos "" myuser
  USER 1000

  # Your other Docker instructions
  ...

When you adopt this practice, ensure that your application and any other processes or scripts within the container have the necessary permissions to run as this user.

Conclusion

As the adoption of containers increases, it becomes crucial to ensure that container images are secure, lightweight, and built from trusted sources. The options described in this post can be a starting point for creating such images. 

#containers #kubernetes

Keep Reading

Take Advantage of the Cloud

Schedule a Call