top of page
Writer's pictureThe Tech Platform

How to write a production-ready dockerfile?



What is DockerFile?

A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession. This page describes the commands you can use in a Dockerfile


Features of Docker:

Docker provides various features, some of which are listed and discussed below.

  1. Faster and easier configuration

  2. Application isolation

  3. Increase in productivity

  4. Swarm

  5. Services

  6. Routing Mesh

  7. Security Management

  8. Rapid scaling of Systems

  9. Better Software Delivery

  10. Software-defined networking

  11. Has the Ability to Reduce the Size


How to write a production-ready dockerfile?

There are a lot of Dockerfile samples on web and developers tend to use those example directly for building applications into containers during development stage. However, the fact is those Dockerfile samples are far from production-ready, which may induce reliablity and security issues if containers created with such Dockerfile are being shipped into production environment. Even though issues may spot out by your security team eventually (or may not), we all should learn how to write a production-ready Dockerfile under the Shift Left approach.


Running as non-root use

Below was the Dockerfile example in building an ASP.NET Core application from docker.com, since this example doesn’t set a user explicity in the Dockerfile, docker will run the command dotnet aspnetapp.dll as root user. This might not be an issue in development stage but that induce container vulnerabilities in production environment.

  FROM mcr.microsoft.com/dotnet/aspnet:5.0
  COPY bin/Release/netcoreapp3.1/publish/ App/
  WORKDIR /App
  ENTRYPOINT ["dotnet", "aspnetapp.dll"]

The suggested way to write the Dockerfile is as below, which will create and run the command dotnet aspnetapp.dll as a non-root user.

  FROM mcr.microsoft.com/dotnet/aspnet:5.0  # Create group app and user app
  RUN addgroup --gid 1000 -S app && adduser --uid 1000 -S app -G app  # Set permissions for app user for the WORKDIR    
  RUN chown -R app:app App/  # Switch to the created user
  USER app
 
  COPY bin/Release/netcoreapp3.1/publish/ App/
  WORKDIR /App
  ENTRYPOINT ["dotnet", "aspnetapp.dll"]


Pin your image verions

Specify a version number for your image called pinning. Dockerfile samples on web often pin their base images to latest which means it will get the most updated version everytime. This may causes issues in production because latest version means it is not the version being well-tested in your development and QA stages. A new version may introduce some unexpected changes in the container which could induce security and reliability issues out of your expectation.


Pin to latest example

FROM node:latest

Pin to specific version example

FROM node:16.2.0


Add healthchecks (not applicable to Kubernetes)

If your application was running on pure docker or docker swarm, HEALTHCHECK command is the right way to go. If your application was running on Kubernetes, you should instead refer to Liveness Probes and Readiness Probes features provided by Kubernetes.


The HEALTHCHECK command can be specified as:

HEALTHCHECK options CMD command

The options are: - interval=DURATION (default 30s) - timeout=DURATION (default 30s) - start-period=DURATION (default: 0s) - retries=N (default 3)


The command can by any command you can run inside your container, usually curl will be used, for example we could use below health check command for a SpringBoot application.

HEALTHCHECK --interval=5s --timeout=3s CMD curl --fail http://localhost:8080/actuator/health || exit 1


Use multi-stage builds

Mutli-stage build is the way to reduce image size by building the image twice.

The first stage is to build the application artifacts by using the base image containing the entire SDK of that programming lanuage (for example, JDK for Java and .Net SDK for .Net Core).


The second stage is to build an image by copying only the application artifacts left in the first stage and leave out anything which is not needed. Usually the image containing only the runtime environment will be used as the base image in this stage.


Below is an Dockerfile example provided by Microsoft using multi-stage build.

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source# copy csproj and restore as distinct layers
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore# copy everything else and build app
COPY aspnetapp/. ./aspnetapp/
WORKDIR /source/aspnetapp
RUN dotnet publish -c release -o /app --no-restore# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "aspnetapp.dll"]

By using the multi-stage build approach, aspnet:6.0 (image containing only the .Net runtime) instead of sdk:6.0 (image containing the entire .Net SDK) is used as the base image in the final stage. This is a desired result because image containing only the runtime often has a reduced size when compared with image containing the entire SDK.


Ensure image with minimal packages

Reducing image size is an important concern when there is space constraints. Moreover, keeping the image size small also means keeping minimal packages installation on your image. Minimal packages installation has the benefit of reducing attack surface in container security perspective.

To achieve minmial packages installation on your image, we should select the approriate base image. The common base image includes:

  • Alpine (probably the smallest option among base images, which is an operating system built by Alpine Linux dedicated for container use)

  • Stretch / Buster / Jessie (buster, jessie or stretch are the codenames for releases of Debian and indicate which release the image is based on)

  • Slim (contains only the minmial packages needed to run your particular applications compared with the full official image)


Adopt container scanning solutions

Container scanning is the process of scanning containers to identify potential vulnerabilities. It is a crucial process in your DevOps workflow and many teams are looking for such solutions to minmize container security threats. Comparing with above mentioned best practices in writing Dockerfile, container scanning solutions should act as the final security guard to detect and aovid any undesired settings rollout into production environment.



Resource: Medium (Marco),


The Tech Platform

Comments


bottom of page