---
title: Docker development best practices
description: Rules of thumb for making your life easier as a Docker application developer
keywords: application, development
---

The following development patterns have proven to be helpful for people
building applications with Docker. 

<!-- markdownlint-disable-next-line -->
If you have discovered something we should add, [let us know]({{% param "repo" %}}/issues/new?template=doc_issue.yml&labels=status%2Ftriage).

## How to keep your images small

Small images are faster to pull over the network and faster to load into
memory when starting containers or services. There are a few rules of thumb to
keep image size small:

- Start with an appropriate base image. For instance, if you need a JDK,
  consider basing your image on a Docker Official Image which includes OpenJDK,
  such as `eclipse-temurin`, rather than building your own image from scratch.

- [Use multistage builds](../build/building/multi-stage.md). For
  instance, you can use the `maven` image to build your Java application, then
  reset to the `tomcat` image and copy the Java artifacts into the correct
  location to deploy your app, all in the same Dockerfile. This means that your
  final image doesn't include all of the libraries and dependencies pulled in by
  the build, but only the artifacts and the environment needed to run them.

  - If you need to use a version of Docker that does not include multistage
    builds, try to reduce the number of layers in your image by minimizing the
    number of separate `RUN` commands in your Dockerfile. You can do this by
    consolidating multiple commands into a single `RUN` line and using your
    shell's mechanisms to combine them together. Consider the following two
    fragments. The first creates two layers in the image, while the second
    only creates one.

    ```dockerfile
    RUN apt-get -y update
    RUN apt-get install -y python
    ```

    ```dockerfile
    RUN apt-get -y update && apt-get install -y python
    ```

- If you have multiple images with a lot in common, consider creating your own
  [base image](../build/building/base-images.md) with the shared
  components, and basing your unique images on that. Docker only needs to load
  the common layers once, and they are cached. This means that your
  derivative images use memory on the Docker host more efficiently and load more
  quickly.

- To keep your production image lean but allow for debugging, consider using the
  production image as the base image for the debug image. Additional testing or
  debugging tooling can be added on top of the production image.

- When building images, always tag them with useful tags which codify version
  information, intended destination (`prod` or `test`, for instance), stability,
  or other information that's useful when deploying the application in
  different environments. Don't rely on the automatically-created `latest` tag.

## Where and how to persist application data

- Avoid storing application data in your container's writable layer using
  [storage drivers](../storage/storagedriver/select-storage-driver.md). This increases the
  size of your container and is less efficient from an I/O perspective than
  using volumes or bind mounts.
- Instead, store data using [volumes](../storage/volumes.md).
- One case where it's appropriate to use
  [bind mounts](../storage/bind-mounts.md) is during development,
  when you may want to mount your source directory or a binary you just built
  into your container. For production, use a volume instead, mounting it into
  the same location as you mounted a bind mount during development.
- For production, use [secrets](../engine/swarm/secrets.md) to store sensitive
  application data used by services, and use [configs](../engine/swarm/configs.md)
  for non-sensitive data such as configuration files. If you currently use
  standalone containers, consider migrating to use single-replica services, so
  that you can take advantage of these service-only features.


## Use CI/CD for testing and deployment

- When you check in a change to source control or create a pull request, use
  [Docker Hub](../docker-hub/builds/index.md) or
  another CI/CD pipeline to automatically build and tag a Docker image and test
  it.

- Take this even further by requiring your development, testing, and
  security teams to [sign images](../reference/cli/docker/trust/_index.md)
  before the teams deploy the images into production. This way, before an image is
  deployed into production, it has been tested and signed off by, for instance,
  development, quality, and security teams.

## Differences in development and production environments

| Development                                                         | Production                                                                                                                                                                                                                                       |
|:--------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Use bind mounts to give your container access to your source code.  | Use volumes to store container data.                                                                                                                                                                                                             |
| Use Docker Desktop for Mac, Linux, or Windows.           | Use Docker Engine, if possible with [userns mapping](../engine/security/userns-remap.md) for greater isolation of Docker processes from host processes.                                                                                          |
