Best Practices When It Comes to Writing Docker Related Files
Here's a few patterns and little things I've picked up after using Docker since 2014. These help maintain Docker driven apps.
Update in 2021: This article was originally posted in mid-2018 and in mid-2021 I gave a talk at DockerCon which goes into way more detail, plus I’ve had a few changes of opinion since then. You may want to skim this article and then check out the live demo video and blog post from DockerCon.
Everything listed below is based on personal experience and opinions. These are things that have worked well for me and the clients I’ve worked with while freelancing over the years.
It’s also worth mentioning that this list always changes based on new experiences and I’ll be updating this post as new patterns / styles emerge.
# Dockerfile
- Use Alpine as a base image unless you can’t due to technical reasons
- Pin versions to at least the minor version, example:
2.5-alpine
not2-alpine
- Add a maintainer
LABEL
to keep tabs on who initially made the image - Only include
ARG
andENV
instructions if you really need them - Use
/app
to store your app’s code and set it as theWORKDIR
(if it makes sense) - When installing packages, take advantage of Docker’s layer caching techniques
- If your app is a web service,
EXPOSE 8000
unless you have a strong reason not to*** - Include a
wget
drivenHEALTHCHECK
(if it makes sense) - Stick to the
[]
syntax when supplying yourCMD
instructions
* This is explained in more detail below under the docker-compose.yml
section.
# docker-compose.yml
- List your services in the order you expect them to start
- Alphabetize each service’s properties
- Double quote all strings and use
{}
for empty hashes / dictionaries - Pin versions to at least the minor version, example:
10.4-alpine
not10-alpine
- Use
.
instead of$PWD
for when you need the current directory’s path - Prefer
build: "."
unless you need to useargs
or some other sub-property - If your service is a web service, publish port
8000
unless it doesn’t make sense to
Alphabetizing your service’s properties, really?
Yep. Some services require having a bunch of properties and not giving them a specific order means you’ll likely order things in different ways across projects or even services.
It’s just 1 more thing to think about, but by having them alphabetized you no longer have to waste brain cycles grouping things up manually and you can scan a list of properties quickly.
Exposing and Publishing on port 8000?
That last one requires an explanation. I work with Flask, Rails, Phoenix and Node apps on a pretty regular basis. Each of these use different ports by default for their app server.
Rails uses 3000, Flask uses 5000, Phoenix uses 4000 and most Express apps use 3000.
When it comes to accessing these services in your browser, it’s confusing
because you always think, wait is it localhost:3000
or localhost:4000
?
By defaulting to 8000
for your web services there’s no more second guessing
yourself. If you have a multi-service app then increment the port by 1, such
as 8001
, 8002
, etc..
# .dockerignore
- Don’t forget to create this file :D
- Don’t forget to add the
.git
folder - Don’t forget to add any sensitive files such as
.env.production
# Example Apps for Popular Web Frameworks
I’ve put together a few example applications that stick to these best practices. You can find them all on https://github.com/nickjj/docker-web-framework-examples.
Fully working Docker Compose based examples that you can reference:
If you don’t see your favorite web framework listed above, open up a PR! This repo is meant to be a community effort where we can work together to make high quality example apps that demonstrate Dockerizing popular web frameworks and libraries.
What are some of your best practices? Let me know below!