avatarAnav Mahajan

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

8051

Abstract

Start the application using pm2</span>

<span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">"pm2-runtime"</span>, <span class="hljs-string">"app.js"</span>]</span></pre></div><p id="1ad0"><b>2. Pitfall: Large Images</b></p><ul><li><i>Explanation</i>: Creating large container images can lead to longer deployment times, increased storage consumption, and unnecessary transfer of unused files.</li><li><i>Alternative</i>: Utilize multi-stage builds to create smaller, optimized images. Build your application in one stage and then copy only the necessary artifacts to the final stage.</li><li><i>Example</i>:</li></ul><div id="6ee1"><pre><span class="hljs-comment"># Better Approach</span>

<span class="hljs-comment"># Multi-stage build example</span> <span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span> AS builder <span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> . .</span> <span class="hljs-keyword">RUN</span><span class="language-bash"> npm install && npm build</span>

<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span> AS final <span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> --from=builder /app/dist ./dist</span></pre></div><p id="2a76"><b>3. Pitfall: Hardcoding Secrets</b></p><ul><li><i>Explanation</i>: Storing sensitive information directly in the Dockerfile can lead to exposure of secrets, security vulnerabilities, and difficulties in managing credentials.</li><li><i>Alternative</i>: Use environment variables to pass sensitive information. These variables can be provided at runtime, ensuring that secrets remain hidden.</li><li><i>Example</i>:</li></ul><div id="4e63"><pre><span class="hljs-comment"># Avoid this</span> ENV <span class="hljs-attribute">DB_PASSWORD</span>=mysecretpassword</pre></div><div id="be94"><pre><span class="hljs-comment"># Better Approach</span>

<span class="hljs-comment"># Set environment variable for sensitive information during build</span>

ARG DATABASE_PASSWORD ENV <span class="hljs-attribute">DB_PASSWORD</span>=<span class="hljs-variable">DATABASE_PASSWORD</span> ENV <span class="hljs-attribute">DB_PASSWORD</span>=<span class="hljs-variable">DATABASE_PASSWORD</span></pre></div><p id="365b"><b>4. Pitfall: Neglecting .dockerignore</b></p><ul><li><i>Explanation</i>: Not using a .dockerignore file can lead to including unnecessary files and directories in the Docker context, which results in larger image sizes and slower builds.</li><li><i>Alternative</i>: Create a .dockerignore file to specify which files and directories to exclude from the build context.</li><li><i>Example</i>:</li></ul><div id="39a4"><pre><span class="hljs-comment"># Avoid: Including unnecessary files</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> . /app</span></pre></div><div id="656e"><pre><span class="hljs-comment"># Better Approach</span> <span class="hljs-comment"># Use a .dockerignore file</span> node_modules <span class="hljs-string">.git</span></pre></div><p id="be93"><b>5. Pitfall: Bloated Layers</b></p><ul><li><i>Explanation</i>: Adding multiple commands within a single RUN instruction can result in bloated image layers, making the image larger than necessary.</li><li><i>Alternative</i>: Combine related commands in a single RUN instruction to minimize the number of layers.</li><li><i>Example</i>:</li></ul><div id="d7e3"><pre><span class="hljs-comment"># Avoid this</span> <span class="hljs-comment"># Creating unnecessary layers</span> <span class="hljs-built_in">RUN</span> apt-<span class="hljs-built_in">get</span> update <span class="hljs-built_in">RUN</span> apt-<span class="hljs-built_in">get</span> install -y curl</pre></div><div id="354e"><pre><span class="hljs-comment"># Better approach</span> <span class="hljs-built_in">RUN</span> apt-<span class="hljs-built_in">get</span> update &&
apt-<span class="hljs-built_in">get</span> install -y package1 package2</pre></div><p id="3e10"><b>6. Pitfall: Excessive Image Layers</b></p><ul><li>Explanation: Adding unnecessary files and directories to an image layer can bloat the image size, affecting deployment speed and efficiency.</li><li>Alternative: Use the COPY command to add only the necessary files and directories to the image layer.</li><li>Example:</li></ul><div id="5818"><pre><span class="hljs-comment"># Avoid this</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> . /app</span>

<span class="hljs-comment"># Better approach</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> package.json package-lock.json /app/</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> src /app/src/</span></pre></div><p id="5bc1"><b>7. Pitfall: Using latest Tag</b></p><ul><li><i>Explanation</i>: Using the latest tag for images can lead to unpredictability, as the image might change without your knowledge.</li><li><i>Alternative</i>: Specify a specific version tag to ensure consistent and predictable behavior when pulling images.</li><li><i>Example</i>:</li></ul><div id="9897"><pre><span class="hljs-comment"># Avoid this</span> FROM <span class="hljs-keyword">node</span><span class="hljs-title">:latest</span>

<span class="hljs-comment"># Better approach</span> FROM <span class="hljs-keyword">node</span><span class="hljs-title">:14</span></pre></div><p id="2783"><b>8. Pitfall: Tight Coupling</b></p><ul><li><i>Explanation</i>: Embedding configuration settings directly within the Dockerfile makes it challenging to update configurations without rebuilding the image.</li><li><i>Alternative</i>: Use environment variables to pass configuration settings. This allows for dynamic configuration without modifying the Dockerfile.</li><li><i>Example</i>:</li></ul><div id="6251"><pre><span class="hljs-comment"># Avoid this</span>

<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>

<span class="hljs-comment"># Set working directory</span> <span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>

<span class="hljs-comment"># Embed configuration settings (Tightly Coupled)</span> <span class="hljs-keyword">ENV</span> API_URL=http://api.example.com <span class="hljs-keyword">ENV</span> LOG_LEVEL=info

<span class="hljs-comment"># Copy package.json and package-lock.json</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> package*.json ./</span>

<span class="hljs-comment"># Install dependencies</span> <span class="hljs-keyword">RUN</span><span class="language-bash"> npm install</span>

<span class="hljs-comment"># Copy application code</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> . .</span>

<span class="hljs-comment"># Start the application</span> <span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"app.js"</span>]</span> </pre></div><div id="423d"><pre><span class="hljs-comment"># Better approach</span>

<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>

<span class="hljs-comment"># Set working directory</span> <span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>

<span class="hljs-comment"># Copy package.json and package-lock.json</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> package*.json ./</span>

<span class="hljs-comment"># Install dependencies</span> <span class="hljs-keyword">RUN</span><span class="language-bash"> npm install</span>

<span class="hljs-comment"># Copy application code</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> . .</span>

<span class="hljs-comment"># Use environment variables for configuration</span> <span class="hljs-keyword">ENV</span> API_URL

Options

<span class="hljs-keyword">ENV</span> LOG_LEVEL

<span class="hljs-comment"># Start the application</span> <span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"app.js"</span>]</span> </pre></div><div id="e2a2"><pre>docker run -e <span class="hljs-string">"API_URL=http://new-api.example.com"</span> -e <span class="hljs-string">"LOG_LEVEL=debug"</span> my-node-app</pre></div><p id="89df"><b>9. Pitfall: Redundant Dependencies</b></p><ul><li><i>Explanation</i>: Installing unnecessary dependencies can increase the image size and introduce security risks.</li><li><i>Alternative</i> : Install only production dependencies. Instead of all dev dependencies which not required in production.</li></ul><div id="708b"><pre><span class="hljs-comment"># Avoid this</span> <span class="hljs-comment"># Unnecessary: Installing development dependencies</span> <span class="hljs-built_in">RUN</span> npm install </pre></div><div id="1453"><pre><span class="hljs-comment"># Better approach</span> <span class="hljs-comment"># Installing only production dependencies</span> <span class="hljs-built_in">RUN</span> npm install <span class="hljs-attribute">--only</span>=production</pre></div><p id="2b0f"><b>10. Pitfall: Hardcoding Dependencies</b></p><ul><li><i>Explanation</i>: Hardcoding specific versions of dependencies in the Dockerfile can lead to outdated packages and potential vulnerabilities.</li><li><i>Alternative: </i>Use a package manager to manage dependencies dynamically.</li></ul><div id="11de"><pre><span class="hljs-comment"># Avoid: Hardcoding dependencies</span> <span class="hljs-attribute">RUN</span> npm install package@<span class="hljs-number">1</span>.<span class="hljs-number">0</span>.<span class="hljs-number">0</span></pre></div><div id="f124"><pre><span class="hljs-comment"># Better approach</span> <span class="hljs-comment"># Use a package manager to install dependencies</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> package*.json ./</span> <span class="hljs-keyword">RUN</span><span class="language-bash"> npm install</span></pre></div><p id="3482"><b>11. Pitfall: Unoptimized COPY Order</b></p><ul><li><i>Explanation</i>: When copying files into a Docker image using the COPY instruction, the order in which you perform these copies can impact the efficiency of image caching and the resulting image size. Docker caches each layer, and if a layer’s contents change, all subsequent layers are invalidated. Inefficient COPY order can lead to more layers being invalidated, which negatively affects caching and increases image size.</li><li><i>Example</i>: Consider a scenario where you copy the application source code before installing dependencies. This is inefficient because any code change will invalidate the dependency installation layer, causing it to be re-run every time you modify the code.</li><li><b><i>DONT DO THIS : </i>Inefficient Unoptimized COPY Order<i>:</i></b></li></ul><div id="2ec8"><pre><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>

<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>

<span class="hljs-comment"># Copying application source code first</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> . /app/</span>

<span class="hljs-comment"># Install dependencies</span> <span class="hljs-keyword">RUN</span><span class="language-bash"> npm install</span>

<span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"app.js"</span>]</span></pre></div><ul><li><b><i>Efficient COPY Order</i></b><i>: </i>To optimize the COPY order, you should copy only the necessary files required for installing dependencies first, allowing Docker to cache this layer. Then copy the rest of the application files, ensuring changes in the application code don’t invalidate the dependency layer.</li></ul><div id="94ad"><pre><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>

<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>

<span class="hljs-comment"># Copying only package files for dependency installation</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> package*.json ./</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> npm-shrinkwrap.json ./</span>

<span class="hljs-comment"># Install dependencies</span> <span class="hljs-keyword">RUN</span><span class="language-bash"> npm install</span>

<span class="hljs-comment"># Copying the rest of the application code</span> <span class="hljs-keyword">COPY</span><span class="language-bash"> . .</span>

<span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"app.js"</span>]</span> </pre></div><p id="6ffd">By understanding and avoiding these pitfalls, you can create efficient, secure, and maintainable Docker images that enhance the reliability and performance of your containerized applications.</p><h1 id="9e65">Complete Production Grade Dockerfile</h1><div id="7d90"><pre><span class="hljs-comment"># Use a specific version of Node.js as an argument</span> ARG <span class="hljs-attribute">NODE_VERSION</span>=14 <span class="hljs-keyword">FROM</span> node:<span class="hljs-variable">${NODE_VERSION}</span>-alpine

<span class="hljs-comment"># Set working directory</span> WORKDIR /app

<span class="hljs-comment"># Copy package.json and package-lock.json</span> COPY package*.json ./ COPY npm-shrinkwrap.json ./

<span class="hljs-comment"># Install production dependencies</span> <span class="hljs-built_in">RUN</span> npm install <span class="hljs-attribute">--only</span>=production

<span class="hljs-comment"># Copy the rest of the application code</span> COPY . .

<span class="hljs-comment"># Set environment variables (can be overridden at runtime)</span> ENV <span class="hljs-attribute">NODE_ENV</span>=<span class="hljs-variable">{NODE_ENV:-production}</span> ENV <span class="hljs-attribute">API_URL</span>=<span class="hljs-variable">{API_URL:-https://api.example.com}</span> ENV <span class="hljs-attribute">LOG_LEVEL</span>=<span class="hljs-variable">{LOG_LEVEL:-info}</span> ENV <span class="hljs-attribute">VERSION</span>=<span class="hljs-variable">{VERSION:-v1.0.0}</span>

<span class="hljs-comment"># Install pm2 globally</span> <span class="hljs-built_in">RUN</span> npm install -g pm2

<span class="hljs-comment"># Start the application with pm2</span> CMD [<span class="hljs-string">"pm2-runtime"</span>, <span class="hljs-string">"app.js"</span>, <span class="hljs-string">"--"</span>, <span class="hljs-string">"-v"</span>, <span class="hljs-string">"<span class="hljs-variable">$VERSION</span>"</span>] </pre></div><div id="9772"><pre>docker build -t my-<span class="hljs-keyword">node</span><span class="hljs-title">-app</span> --build-arg <span class="hljs-attr">NODE_VERSION=</span><span class="hljs-number">14</span> . docker run -e <span class="hljs-string">"VERSION=v1.0.1"</span> my-<span class="hljs-keyword">node</span><span class="hljs-title">-app</span> </pre></div><h1 id="c9e7">Conclusion:</h1><p id="bc9b">The progression from physical servers to containers marks a significant shift in software deployment. Virtualization laid the groundwork, while containers revolutionized efficiency, scalability, and consistency. Leveraging Docker’s options and avoiding common pitfalls empower developers to create production-ready applications that thrive in the containerized ecosystem. By embracing this evolution, software deployment becomes agile, scalable, and responsive to modern development demands.</p><p id="38e2"><b><i>Follow me on LinkedIn for more such content:</i></b><i> <a href="https://au.linkedin.com/in/anav-mahajan-a9b5a376?trk=profile-badge">https://au.linkedin.com/in/anav-mahajan-a9b5a376?trk=profile-badge</a></i></p></article></body>

Unlocking Docker’s Potential: Best Practices, Common Pitfalls, and Optimal Dockerfile Strategies

Introduction

In the ever-evolving landscape of software deployment, the journey from physical servers to containers has revolutionized how applications are built, deployed, and scaled.

This article explores the transition through virtualization to containerization, highlighting the benefits and best practices along the way.

From Physical Servers to Virtual Machines

In the era of physical servers, applications were tied to specific hardware, leading to inefficient resource utilization and scalability challenges. The introduction of virtual machines (VMs) transformed this landscape. By abstracting hardware into virtual environments, VMs offered isolation, scalability, and reduced hardware costs. Transitioning involved installing a hypervisor on physical servers, which enabled the creation of multiple VMs running various operating systems on a single physical machine.

From Virtual Machines to Containers

While VMs were a leap forward, they still carried some overhead due to their complete OS virtualization. Containers emerged as a lightweight alternative. Containers package an application, its runtime, libraries, and dependencies into a single unit. Unlike VMs, containers share the host OS kernel, making them more efficient and portable. Docker, a pioneer in containerization, simplified the packaging, distribution, and deployment of applications.

Benefits of Containers

Containers bring numerous benefits:

  • Resource Efficiency: Containers share resources, optimizing hardware utilization.
  • Rapid Deployment: Containers boot quickly, enabling agile development and scaling.
  • Consistency: Applications run consistently across various environments.
  • Isolation: Containers isolate applications from one another, enhancing security.
  • Scalability: Containers can scale horizontally with ease.

Dockerfile 101

Docker offers several crucial options to optimize containerisation.

Here are the explanations for each option to be used in writing dockerfile.

1. WORKDIR

Explanation: The `WORKDIR` instruction sets the working directory for subsequent instructions in the Dockerfile.

Use Case: It helps avoid specifying absolute paths for every command, making the Dockerfile more readable and maintainable.

Example:

WORKDIR /app

2. EXPOSE

Explanation: The `EXPOSE` instruction informs Docker that the container will listen on specific ports at runtime, but it doesn’t actually publish the ports.

Use Case: It serves as documentation for users of the image about which ports are intended to be published.

EXPOSE 8080

3. ENV

Explanation: The `ENV` instruction sets environment variables within the container, which can be used to customize container behavior.

ENV NODE_ENV=production

4. CMD

Explanation: The `CMD` instruction provides a default command to run when the container starts. It can be overridden during container runtime.

CMD [“npm”, “start”]

5. ENTRYPOINT

Explanation: The `ENTRYPOINT` instruction configures the container to run as an executable, accepting command line arguments.

ENTRYPOINT [“npm”, “start”]

6. USER

Explanation: The `USER` instruction sets the user context within the image for subsequent instructions.

USER node

7. COPY

Explanation: The `COPY` instruction copies files or directories from the host machine to the image filesystem.

COPY app.js /app/

8. ADD

Explanation: The `ADD` instruction is similar to `COPY` but also supports extracting archives from URLs and copying local files to remote locations.

ADD https://example.com/files/app.zip /app/

9. VOLUME

Explanation: The `VOLUME` instruction creates a mount point for external volumes that can be used to persist data.

VOLUME /data

10. ARG

Explanation: The `ARG` instruction defines variables that can be passed to the builder with the `docker build` command using the ` – build-arg` flag.

ARG VERSION=1.0

Common Dockerfile Pitfalls and Solutions

1. Pitfall: Running Multiple Processes

  • Explanation: Running multiple processes within a single container contradicts the container philosophy, which advocates running a single main process. This can lead to complications in process management, resource utilization, and debugging.
  • Alternative: Use a process manager like `pm2` to manage multiple processes within a container or separate services into multiple containers using a container orchestration tool like Docker Compose or Kubernetes.
  • Example:
# Don’t do this

CMD npm start && redis-server
# Better Approach

FROM node:14

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy application code
COPY . .

# Install pm2 globally
RUN npm install -g pm2

# Start the application using pm2
CMD ["pm2-runtime", "app.js"]

2. Pitfall: Large Images

  • Explanation: Creating large container images can lead to longer deployment times, increased storage consumption, and unnecessary transfer of unused files.
  • Alternative: Utilize multi-stage builds to create smaller, optimized images. Build your application in one stage and then copy only the necessary artifacts to the final stage.
  • Example:
# Better Approach

# Multi-stage build example
FROM node:14 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm build

FROM node:14 AS final
WORKDIR /app
COPY --from=builder /app/dist ./dist

3. Pitfall: Hardcoding Secrets

  • Explanation: Storing sensitive information directly in the Dockerfile can lead to exposure of secrets, security vulnerabilities, and difficulties in managing credentials.
  • Alternative: Use environment variables to pass sensitive information. These variables can be provided at runtime, ensuring that secrets remain hidden.
  • Example:
# Avoid this
ENV DB_PASSWORD=mysecretpassword
# Better Approach

# Set environment variable for sensitive information during build

ARG DATABASE_PASSWORD
ENV DB_PASSWORD=$DATABASE_PASSWORD
ENV DB_PASSWORD=$DATABASE_PASSWORD

4. Pitfall: Neglecting .dockerignore

  • Explanation: Not using a `.dockerignore` file can lead to including unnecessary files and directories in the Docker context, which results in larger image sizes and slower builds.
  • Alternative: Create a `.dockerignore` file to specify which files and directories to exclude from the build context.
  • Example:
# Avoid: Including unnecessary files
COPY . /app
# Better Approach
# Use a .dockerignore file
node_modules
.git

5. Pitfall: Bloated Layers

  • Explanation: Adding multiple commands within a single `RUN` instruction can result in bloated image layers, making the image larger than necessary.
  • Alternative: Combine related commands in a single `RUN` instruction to minimize the number of layers.
  • Example:
# Avoid this
# Creating unnecessary layers
RUN apt-get update
RUN apt-get install -y curl
# Better approach
RUN apt-get update && \
    apt-get install -y package1 package2

6. Pitfall: Excessive Image Layers

  • Explanation: Adding unnecessary files and directories to an image layer can bloat the image size, affecting deployment speed and efficiency.
  • Alternative: Use the `COPY` command to add only the necessary files and directories to the image layer.
  • Example:
# Avoid this
COPY . /app

# Better approach
COPY package.json package-lock.json /app/
COPY src /app/src/

7. Pitfall: Using `latest` Tag

  • Explanation: Using the `latest` tag for images can lead to unpredictability, as the image might change without your knowledge.
  • Alternative: Specify a specific version tag to ensure consistent and predictable behavior when pulling images.
  • Example:
# Avoid this
FROM node:latest

# Better approach
FROM node:14

8. Pitfall: Tight Coupling

  • Explanation: Embedding configuration settings directly within the Dockerfile makes it challenging to update configurations without rebuilding the image.
  • Alternative: Use environment variables to pass configuration settings. This allows for dynamic configuration without modifying the Dockerfile.
  • Example:
# Avoid this

FROM node:14

# Set working directory
WORKDIR /app

# Embed configuration settings (Tightly Coupled)
ENV API_URL=http://api.example.com
ENV LOG_LEVEL=info

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy application code
COPY . .

# Start the application
CMD ["node", "app.js"]
# Better approach

FROM node:14

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy application code
COPY . .

# Use environment variables for configuration
ENV API_URL
ENV LOG_LEVEL

# Start the application
CMD ["node", "app.js"]
docker run -e "API_URL=http://new-api.example.com" -e "LOG_LEVEL=debug" my-node-app

9. Pitfall: Redundant Dependencies

  • Explanation: Installing unnecessary dependencies can increase the image size and introduce security risks.
  • Alternative : Install only production dependencies. Instead of all dev dependencies which not required in production.
# Avoid this
# Unnecessary: Installing development dependencies
RUN npm install
# Better approach
# Installing only production dependencies
RUN npm install --only=production

10. Pitfall: Hardcoding Dependencies

  • Explanation: Hardcoding specific versions of dependencies in the Dockerfile can lead to outdated packages and potential vulnerabilities.
  • Alternative: Use a package manager to manage dependencies dynamically.
# Avoid: Hardcoding dependencies
RUN npm install package@1.0.0
# Better approach
# Use a package manager to install dependencies
COPY package*.json ./
RUN npm install

11. Pitfall: Unoptimized COPY Order

  • Explanation: When copying files into a Docker image using the COPY instruction, the order in which you perform these copies can impact the efficiency of image caching and the resulting image size. Docker caches each layer, and if a layer’s contents change, all subsequent layers are invalidated. Inefficient COPY order can lead to more layers being invalidated, which negatively affects caching and increases image size.
  • Example: Consider a scenario where you copy the application source code before installing dependencies. This is inefficient because any code change will invalidate the dependency installation layer, causing it to be re-run every time you modify the code.
  • DONT DO THIS : Inefficient Unoptimized COPY Order:
FROM node:14

WORKDIR /app

# Copying application source code first
COPY . /app/

# Install dependencies
RUN npm install

CMD ["node", "app.js"]
  • Efficient COPY Order: To optimize the COPY order, you should copy only the necessary files required for installing dependencies first, allowing Docker to cache this layer. Then copy the rest of the application files, ensuring changes in the application code don’t invalidate the dependency layer.
FROM node:14

WORKDIR /app

# Copying only package files for dependency installation
COPY package*.json ./
COPY npm-shrinkwrap.json ./

# Install dependencies
RUN npm install

# Copying the rest of the application code
COPY . .

CMD ["node", "app.js"]

By understanding and avoiding these pitfalls, you can create efficient, secure, and maintainable Docker images that enhance the reliability and performance of your containerized applications.

Complete Production Grade Dockerfile

# Use a specific version of Node.js as an argument
ARG NODE_VERSION=14
FROM node:${NODE_VERSION}-alpine

# Set working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./
COPY npm-shrinkwrap.json ./

# Install production dependencies
RUN npm install --only=production

# Copy the rest of the application code
COPY . .

# Set environment variables (can be overridden at runtime)
ENV NODE_ENV=${NODE_ENV:-production}
ENV API_URL=${API_URL:-https://api.example.com}
ENV LOG_LEVEL=${LOG_LEVEL:-info}
ENV VERSION=${VERSION:-v1.0.0}

# Install pm2 globally
RUN npm install -g pm2

# Start the application with pm2
CMD ["pm2-runtime", "app.js", "--", "-v", "$VERSION"]
docker build -t my-node-app --build-arg NODE_VERSION=14 .
docker run -e "VERSION=v1.0.1" my-node-app

Conclusion:

The progression from physical servers to containers marks a significant shift in software deployment. Virtualization laid the groundwork, while containers revolutionized efficiency, scalability, and consistency. Leveraging Docker’s options and avoiding common pitfalls empower developers to create production-ready applications that thrive in the containerized ecosystem. By embracing this evolution, software deployment becomes agile, scalable, and responsive to modern development demands.

Follow me on LinkedIn for more such content: https://au.linkedin.com/in/anav-mahajan-a9b5a376?trk=profile-badge

Docker
Containers
DevOps
Optimization
Recommended from ReadMedium