The Art of Creating Container Images and Best Practices

Saravanan Gnanaguru - Jul 29 - - Dev Community

The Art of Creating Container Images

Introduction

We are in the era of the evolving landscape of software product development, So the need for efficient, consistent, and scalable deployment methods has been more critical.

One of the most significant advancements in this area is the use of containers. Containers have revolutionized the way we build, package, and deploy applications, offering a level of flexibility and reliability that traditional methods often lack.

It is one of key automation pillar in DevOps Process Automation (CI/CD), that helps the developed frozen code has been released. It is making sure nothing gets changed in code once it is packaged into container images.

In this blog, we'll explore the concept of containers, delve into the traditional approach of application packaging, and highlight the best practices for creating container images. Additionally, we'll provide examples of packaging React and Java-based applications using containers.


What are Containers?

Containers are lightweight, portable units of software that package an application and its dependencies together, ensuring that it runs consistently across different computing environments. Unlike virtual machines, containers share the host system's kernel, which makes them more efficient in terms of resource usage. Containers can be run on any system that supports the container runtime, making them an ideal choice for modern, cloud-native application development and deployment.

Docker Born

Packaging the Application: A Traditional Approach

Before the advent of containers, applications were typically packaged and deployed using traditional methods. This often involved creating installation packages that bundled the application binaries along with necessary libraries and configuration files. These packages were then installed on target systems, where the application would run. While this approach worked for many years, it had several inherent limitations.

Disadvantages of the Old Approach

  1. Inconsistent Environments: Traditional packaging methods often led to inconsistencies between development, testing, and production environments. This could cause applications to behave differently depending on where they were running. There is a chance that developers over-writes the finalised packages that are about to be deployed in release environments, causing the delay in product or feature releases.

  2. Dependency Conflicts: Managing dependencies was a significant challenge. Different applications might require different versions of the same library, leading to conflicts and "dependency hell."

  3. Resource Overhead: Traditional methods typically required separate instances of operating systems for each application, leading to high resource consumption and inefficiencies.

  4. Complex Deployment: The deployment process was often complex and error-prone, requiring manual intervention and detailed configuration.

Before and after DevOps

How Containers Help in Overcoming the Shortfall of Traditional Approach

Containers address many of the shortcomings of traditional application packaging methods:

  1. Consistency: By packaging the application and its dependencies together, containers ensure that the application runs the same way, regardless of where it is deployed.

  2. Isolation: Containers provide process and filesystem isolation, reducing the risk of dependency conflicts and enhancing security.

  3. Efficiency: Containers share the host system's kernel and resources, making them more efficient than virtual machines. This allows for higher density and better resource utilization.

  4. Simplified Deployment: Containers can be easily deployed, scaled, and managed using container orchestration platforms like Kubernetes. This simplifies the deployment process and reduces the risk of errors.


Container Image Creation Best Practices

Creating efficient and secure container images is crucial for leveraging the full benefits of containerization. Here are some best practices to follow:

Low Image Size

  1. Use Minimal Base Images: Start with a minimal base image, such as alpine or distroless, to reduce the overall image size.

  2. Multi-stage Builds: Use multi-stage builds to separate the build environment from the runtime environment. This helps in keeping the final image lightweight by excluding unnecessary build tools and dependencies.

  3. Remove Unnecessary Files: Clean up temporary files, cache, and other unnecessary files during the image build process.

Less/No Vulnerable Images

  1. Regular Updates: Regularly update the base images and dependencies to include the latest security patches and updates.

  2. Security Scanning: Use tools like Trivy or Clair to scan images for known vulnerabilities before deploying them.

  3. Minimal Permissions: Ensure that the application runs with the least privileges necessary to reduce potential attack surfaces.

Be Cautious in Opening Ports

  1. Limit Exposed Ports: Only expose the ports that are necessary for the application to function. Avoid exposing unnecessary ports to reduce the attack surface.

  2. Use Non-standard Ports: When possible, use non-standard ports to make it harder for attackers to find and exploit open services.

Follow Security Best Practices

  1. Use Non-root Users: Run applications as non-root users within the container to minimize the impact of a potential security breach.

  2. Environment Variables: Avoid hardcoding sensitive information in the image. Use environment variables to pass sensitive data at runtime.

  3. Network Policies: Implement network policies to control the traffic between containers, enhancing security within the containerized environment.


Example of Packaging React and Java-based Application

Let's look at examples of packaging a React application and a Java-based application using Docker.

Installing Docker

Before you can create and push images, you need to have Docker installed on your machine. You can follow the official Docker installation guide for your operating system here.

Packaging a React Application

Let us consider a React based web application we have. Here is what the container creation Dockerfile will look like,

  1. Dockerfile:

This is a multi-stage Dockerfile. Where we have Two stages, and Stage 2 is dependent on Stage 1 Build.

Dockerfile has to be added into the Project Home path.

```dockerfile
# Stage 1: Build the React app
FROM node:14-alpine as build

WORKDIR /app

COPY package.json ./
COPY package-lock.json ./

RUN npm install

COPY . ./

RUN npm run build

# Stage 2: Serve the React app using Nginx
FROM nginx:alpine

COPY --from=build /app/build /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
```
Enter fullscreen mode Exit fullscreen mode
  1. Build and Run:

Once the Docker file is created, we can run the commands below to create the Image in user Workstation.

```sh
docker build -t my-react-app .
docker run -d -p 80:80 my-react-app
```
Enter fullscreen mode Exit fullscreen mode

Packaging a Java-based Application

Let us consider another application, which is a Java based web application. So here is what the container creation Dockerfile will look like,

  1. Dockerfile:

This is also a multi-stage Dockerfile. Where we have Two stages, Stage 1 build code and Stage 2 is dependent on Stage 1 Build.

Dockerfile has to be added into the Project Home path.

```dockerfile
# Stage 1: Build the Java app
FROM maven:3.8.1-jdk-11 as build

WORKDIR /app

COPY pom.xml ./
COPY src ./src

RUN mvn clean package -DskipTests

# Stage 2: Run the Java app
FROM openjdk:11-jre-slim

COPY --from=build /app/target/my-java-app.jar /app/my-java-app.jar

EXPOSE 8080

CMD ["java", "-jar", "/app/my-java-app.jar"]
```
Enter fullscreen mode Exit fullscreen mode
  1. Build and Run:

Once the Docker file is created, we can run the commands below to create the Image in user Workstation.

```sh
docker build -t my-java-app .
docker run -d -p 8080:8080 my-java-app
</code></pre></div><h2>
  <a name="pushing-images-to-docker-hub-or-cloud-registry" href="#pushing-images-to-docker-hub-or-cloud-registry">
  </a>
  Pushing Images to Docker Hub or Cloud Registry
</h2>

<p>Once your container images are built and tested locally, you often need to push them to a container registry for storage and deployment to other environment. Here’s how to push images to different registries.</p>
<h3>
  <a name="pushing-images-to-docker-hub" href="#pushing-images-to-docker-hub">
  </a>
  Pushing Images to Docker Hub
</h3>

<ol>
<li><p><strong>Create a Docker Hub Account</strong>: If you don't already have one, go to <a href="https://hub.docker.com/">Docker Hub</a> and create an account.</p></li>
<li>
<p><strong>Login to Docker Hub</strong>:</p>
<pre class="highlight shell"><code>docker login
</code></pre>
<p>You will be prompted to enter your Docker Hub username and password.</p>
</li>
<li>
<p><strong>Tag Your Image</strong>:</p>
<pre class="highlight shell"><code>docker tag my-image:latest your-dockerhub-username/my-image:latest
</code></pre>
</li>
<li>
<p><strong>Push the Image</strong>:</p>
<pre class="highlight shell"><code>docker push your-dockerhub-username/my-image:latest
</code></pre>
</li>
</ol>
<h3>
  <a name="pushing-images-to-google-container-registry-gcr" href="#pushing-images-to-google-container-registry-gcr">
  </a>
  Pushing Images to Google Container Registry (GCR)
</h3>

<ol>
<li>
<p><strong>Set Up GCloud</strong>: Install the Google Cloud SDK and authenticate.</p>
<pre class="highlight shell"><code>gcloud auth login
gcloud auth configure-docker
</code></pre>
</li>
<li>
<p><strong>Tag Your Image</strong>:</p>
<pre class="highlight shell"><code>docker tag my-image:latest gcr.io/your-project-id/my-image:latest
</code></pre>
</li>
<li>
<p><strong>Push the Image</strong>:</p>
<pre class="highlight shell"><code>docker push gcr.io/your-project-id/my-image:latest
</code></pre>
</li>
</ol>
<h3>
  <a name="pushing-images-to-amazon-elastic-container-registry-ecr" href="#pushing-images-to-amazon-elastic-container-registry-ecr">
  </a>
  Pushing Images to Amazon Elastic Container Registry (ECR)
</h3>

<ol>
<li>
<p><strong>Create an ECR Repository</strong>: Use the AWS Management Console or CLI to create a repository.</p>
<pre class="highlight shell"><code>aws ecr create-repository <span class="nt">--repository-name</span> my-repo
</code></pre>
</li>
<li>
<p><strong>Authenticate Docker to Your ECR</strong>:</p>
<pre class="highlight shell"><code>aws ecr get-login-password <span class="nt">--region</span> your-region | docker login <span class="nt">--username</span> AWS <span class="nt">--password-stdin</span> your-account-id.dkr.ecr.your-region.amazonaws.com
</code></pre>
</li>
<li>
<p><strong>Tag Your Image</strong>:</p>
<pre class="highlight shell"><code>docker tag my-image:latest your-account-id.dkr.ecr.your-region.amazonaws.com/my-repo:latest
</code></pre>
</li>
<li>
<p><strong>Push the Image</strong>:</p>
<pre class="highlight shell"><code>docker push your-account-id.dkr.ecr.your-region.amazonaws.com/my-repo:latest
</code></pre>
</li>
</ol>
<h3>
  <a name="pushing-images-to-azure-container-registry-acr" href="#pushing-images-to-azure-container-registry-acr">
  </a>
  Pushing Images to Azure Container Registry (ACR)
</h3>

<ol>
<li>
<p><strong>Create an ACR</strong>: Use the Azure CLI to create a registry.</p>
<pre class="highlight shell"><code>az acr create <span class="nt">--resource-group</span> myResourceGroup <span class="nt">--name</span> myACR <span class="nt">--sku</span> Basic
</code></pre>
</li>
<li>
<p><strong>Login to ACR</strong>:</p>
<pre class="highlight shell"><code>az acr login <span class="nt">--name</span> myACR
</code></pre>
</li>
<li>
<p><strong>Tag Your Image</strong>:</p>
<pre class="highlight shell"><code>docker tag my-image:latest myACR.azurecr.io/my-repo:latest
</code></pre>
</li>
<li>
<p><strong>Push the Image</strong>:</p>
<pre class="highlight shell"><code>docker push myACR.azurecr.io/my-repo:latest
</code></pre>
</li>
</ol>
<h3>
  <a name="summary-of-steps" href="#summary-of-steps">
  </a>
  Summary of Steps
</h3>

<ol>
<li>
<strong>Create an Account/Repository</strong>: Create an account on the</li>
</ol>

<p>desired registry platform and create a repository if necessary.</p>

<ol>
<li>
<strong>Login to the Registry</strong>: Use CLI commands to authenticate your Docker client with the registry.</li>
<li>
<strong>Tag Your Image</strong>: Properly tag your Docker image to match the registry's naming conventions.</li>
<li>
<strong>Push the Image</strong>: Use the <code>docker push</code> command to upload your image to the registry.</li>
</ol>
<h2>
  <a name="conclusion" href="#conclusion">
  </a>
  Conclusion
</h2>

<p>Containers have transformed the way we package and deploy applications, offering significant advantages over traditional methods. By following best practices for container image creation, we can build efficient, secure, and reliable containerized applications. Whether you're working with modern JavaScript frameworks like React or more traditional Java applications, containerization can streamline your development and deployment processes, ensuring consistency and scalability across different environments. Start leveraging containers today to take your application development to the next level.</p>
<h2>
  <a name="suggested-next-steps" href="#suggested-next-steps">
  </a>
  Suggested Next Steps
</h2>

<ol>
<li>Explore container orchestration platforms like Kubernetes.</li>
<li>Integrate continuous integration and continuous deployment (CI/CD) pipelines with containerization.</li>
<li>Learn about service mesh technologies for managing microservices within a containerized environment.</li>
</ol>

<p>By embracing these technologies and practices, you can further enhance the efficiency and scalability of your applications.</p>
<h2>
  <a name="reach-out-to-me" href="#reach-out-to-me">
  </a>
  Reach out to me
</h2>

<p><a href="https://www.linkedin.com/in/saravanan-gnanaguru">LinkedIn</a></p>

<p><a href="//github.com/chefgs">GitHub</a></p>
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .