Hardening a Docker Image

Hardening a Docker Image

A brief guide to hardening your docker image to reduce chances of your system getting pwned.


Hardening refers to decrease the potential of a system getting exploited by reducing the attack surface. It may range from adhering different policies like Least Privilege, Zero Trust and also contain different steps like automatic security updates, firewalls, rotating passwords etc.

For hardening a system, basically the system-ops should cut-off unnecessary provisioned processes, services and vast majority of other resources currently used by the system unless they're bare minimum or essential for the system to function.

There are a million steps to be taken in order to prepare an absolutely stone hardened system but we'll look at some essential steps in hardening a docker image so that next time someone tries to own your docker container, they'll have hard time.

Limit the build data with .dockerignore or unnecessary copy.

Most of the times when building a docker image, it's a wild practice to move a directory directly inside the image, but this increases the attack surface as .env files, configuration folders, log files may be moved inside the docker image which gives the attacker more information already present inside the docker container regarding the system.

Even if the data are too many to be copied without doing a wild-card copy, I'd suggest to use a .dockerignore file which behaves similar to .gitignore and it stops the files from being copied which are mentioned there.

Use smaller images or scratch if possible.

Using a larger docker bases which consist of a thousand of tools your application doesn't need to function but increase the attack surface as there's chances of them having security issues is a great way of helping the hacker.

Try to use a very small image. Also, don't make a nightmare for developer by using images like alpine when the application is supposed to use very complicated libraries as alpine builds everything using musl and sometimes it's a nightmare.

My preferred docker images are slim builds of debian. Also, it's a lot easier when you're using statically compiled languages like go, which can run natively inside docker container when you just ship a container with only the binary. Think about the attack surface reduction when everything the filesystem contains is a single binary.

Downgrade to non-privileged User.

Getting rid of the root user in a docker image is just a two line configuration change. By doing just this simple change, we introduced a whole new problem of privilege escalation to the potential attacker.

Mount host file system as read-only.

Often times, we need to mount host data for reading configuration, or many other reasons for the docker container to function properly. For this, if we mount the host file system with full RW access chances are the potential attacker will misbehave with the mounted file system. To mitigate to some extent, we should mount the file system in read-only mode. We should reduce the access in the host file system by stripping down to the only required folder / file.

Don't expose unnecessary ports.

In development environment, it's a good idea to expose multiple ports, as the developer may require to run remote debugging in the container, or an extra development only network access for whatever reason but those ports should be strictly shut down while running with live users. It's a good idea to expose only the port listened by the main process running inside the docker container.

Never run in privileged mode

This requires no explanation. Running a container in privileged mode means it can do basically anything the host system. This is the worst thing through which you not only risk the container but also risk the host system.

Other Extra Suggestions

Enable Scan on Push in ECR.

Elastic Container Registry (ECR) service provided by Amazon can scan for security issues in your docker images as soon as you push them. This enables you to know about the potential threats in the docker image and quickly update / patch the packages which have CVEs assigned to them.