Skip to main content

Command Palette

Search for a command to run...

Production-Ready Dockerfile: Best Practices for Building Reliable and Secure Images

Updated
4 min read
Production-Ready Dockerfile: Best Practices for Building Reliable and Secure Images
R

An experienced DevOps Engineer understands the integration of operations and development in order to deliver code to customers quickly. Has Cloud and monitoring process experience, as well as DevOps development in Windows, Mac, and Linux systems.

When building Docker images for production, it's crucial to follow best practices to ensure your images are secure, efficient, and easy to maintain. In this post, we'll explore some essential tips and tricks for writing a production-ready Dockerfile.

Docker Tips And Best Practices - meirg

Use a Minimal Base Image

Using a minimal base image reduces the attack surface and keeps your image small. Popular choices include Alpine Linux, Debian Slim, and distroless images.

# Use a minimal base image
FROM alpine:3.14

# Install necessary packages
RUN apk add --no-cache curl wget

Keep the Dockerfile Simple and Concise

Avoid complex Dockerfiles with multiple stages and unnecessary instructions. Keep it simple and focused on the task at hand.

# Start from a minimal base image
FROM alpine:3.14

# Install necessary packages
RUN apk add --no-cache nodejs npm

# Copy application files
COPY . /app

# Set the working directory
WORKDIR /app

# Install dependencies
RUN npm install

# Expose the application port
EXPOSE 3000

# Command to run the application
CMD ["node", "index.js"]

Use Multi-Stage Builds

Multi-stage builds allow you to build and compile code in one stage and copy only the necessary artifacts to the final image.

# Stage 1: Build
FROM node:16 AS builder

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

# Stage 2: Run
FROM node:16-slim

WORKDIR /app
COPY --from=builder /app .

EXPOSE 3000
CMD ["node", "index.js"]

Avoid Installing Unnecessary Packages

Only install packages and dependencies essential for your application. This reduces the image size and attack surface.

# Use a minimal base image
FROM alpine:3.14

# Install only necessary packages
RUN apk add --no-cache nodejs npm

Use Environment Variables

Environment variables make your Dockerfile more flexible and easier to maintain.

# Use a minimal base image
FROM alpine:3.14

# Install necessary packages
RUN apk add --no-cache nodejs npm

# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000

# Copy application files
COPY . /app

# Set the working directory
WORKDIR /app

# Install dependencies
RUN npm install

# Expose the application port
EXPOSE $PORT

# Command to run the application
CMD ["node", "index.js"]

Expose Only Necessary Ports

Only expose ports necessary for your application to reduce the attack surface.

# Use a minimal base image
FROM alpine:3.14

# Install necessary packages
RUN apk add --no-cache nodejs npm

# Copy application files
COPY . /app

# Set the working directory
WORKDIR /app

# Install dependencies
RUN npm install

# Expose only the necessary port
EXPOSE 3000

# Command to run the application
CMD ["node", "index.js"]

Use a Non-Root User

Running your application as a non-root user improves security and reduces the attack surface.

# Use a minimal base image
FROM alpine:3.14

# Add a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# Switch to the non-root user
USER appuser

# Set the working directory
WORKDIR /app

# Copy application files
COPY . .

# Install necessary packages
RUN apk add --no-cache nodejs npm && npm install

# Expose the application port
EXPOSE 3000

# Command to run the application
CMD ["node", "index.js"]

Keep the Image Small

Smaller images reduce storage requirements and improve deployment times.

# Use a minimal base image
FROM alpine:3.14

# Install necessary packages
RUN apk add --no-cache nodejs npm

# Copy application files
COPY . /app

# Set the working directory
WORKDIR /app

# Install dependencies
RUN npm install

# Expose the application port
EXPOSE 3000

# Command to run the application
CMD ["node", "index.js"]

Use a Consistent Naming Convention

Use a consistent naming convention for your Docker images and containers.

Docker build -t myorg/myapp:latest

Use a Linter to Check the Dockerfile

Linters like Hadolint and Dockerfilelint help identify errors and improve the quality of your Dockerfile.

podman  run --rm -i ghcr.io/hadolint/hadolint < Dockerfile

###Output Like below 
-:5 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
-:5 DL3015 info: Avoid additional packages by specifying `--no-install-recommends`
-:5 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
-:25 DL3028 warning: Pin versions in gem install. Instead of `gem install <gem>` use `gem install <gem>:<version>`
-:41 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
-:41 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
-:41 DL3015 info: Avoid additional packages by specifying `--no-install-recommends`

Conclusion

By following these best practices, you can ensure your Docker images are secure, efficient, and easy to maintain. Remember to keep your Dockerfile simple, use minimal base images, and avoid unnecessary packages and exposures.

More from this blog

DevOps Simplified

39 posts

An experienced DevOps Engineer understands the integration of operations and development in order to deliver code to customers quickly. Has Cloud and monitoring process experience, as well as DevOps