├── .gitignore
├── python
├── .dockerignore
├── app
│ ├── ignored.py
│ └── main.py
├── requirements.txt
└── Dockerfile
├── TODO
├── java
├── .dockerignore
├── app
│ ├── META-INF
│ │ └── MANIFEST.MF
│ └── echoserver
│ │ ├── Ignored.java
│ │ └── Server.java
└── Dockerfile
├── Makefile
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
--------------------------------------------------------------------------------
/python/.dockerignore:
--------------------------------------------------------------------------------
1 | app/ignored.py
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | - Reference files for other languages
2 |
--------------------------------------------------------------------------------
/java/.dockerignore:
--------------------------------------------------------------------------------
1 | app/echoserver/Ignored.java
--------------------------------------------------------------------------------
/java/app/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Main-Class: echoserver.Server
--------------------------------------------------------------------------------
/python/app/ignored.py:
--------------------------------------------------------------------------------
1 | # This file will not be copied. See .dockerignore
--------------------------------------------------------------------------------
/java/app/echoserver/Ignored.java:
--------------------------------------------------------------------------------
1 | // This file will not be copied. See .dockerignore
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all:
2 | # Remove comments and blank lines
3 | cat java/Dockerfile | sed -e 's/\s*#.*$$//' | sed -e '/^\s*$$/d' > Dockerfile-prod-java
4 | cat python/Dockerfile | sed -e 's/\s*#.*$$//' | sed -e '/^\s*$$/d' > Dockerfile-prod-python
5 | clean:
6 | rm -f Dockerfile-prod-*
7 |
--------------------------------------------------------------------------------
/python/requirements.txt:
--------------------------------------------------------------------------------
1 | # Always pin versions of dependenies:
2 | # 1. This makes a build reproducible. Otherwise, latest versions of dependencies can potentially break your application due to
3 | # incompatibilities
4 | # 2. This works as Docker cache "busting" -- a technque that forces updating a cache for the image. Otherwise, pip will not check for new
5 | # versions
6 | boto3==1.10.37
--------------------------------------------------------------------------------
/python/app/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/env/python3
2 | import boto3, sys, signal, socket
3 | from http.server import BaseHTTPRequestHandler, HTTPServer
4 | from time import sleep
5 |
6 | def termination_handler(sig, frame):
7 | print('Shutting down gracefully...')
8 | sys.exit(0)
9 |
10 | def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
11 | server_address = ('', 8080)
12 | httpd = server_class(server_address, handler_class)
13 | httpd.serve_forever()
14 |
15 | if __name__ == "__main__":
16 | print("Initializing...")
17 | signal.signal(signal.SIGINT, termination_handler)
18 | s3 = boto3.resource('s3')
19 | print("Serving web content on 0.0.0.0:8080")
20 | run()
--------------------------------------------------------------------------------
/java/app/echoserver/Server.java:
--------------------------------------------------------------------------------
1 | // Source: https://github.com/ansman/servers/blob/master/java/Server.java by @ansman
2 | // javac Server.java && java Server [port=8080] [host=127.0.0.1]
3 | package echoserver;
4 |
5 | import java.net.*;
6 | import java.io.*;
7 |
8 | public class Server {
9 | public static void main(String[] argv) throws IOException {
10 | String host = "0.0.0.0";
11 | short port = 8080;
12 |
13 | if(argv.length >= 2)
14 | host = argv[1];
15 |
16 | if(argv.length >= 1)
17 | port = Short.parseShort(argv[0]);
18 |
19 | ServerSocket server = null;
20 |
21 | try {
22 | server = new ServerSocket(port, 0, InetAddress.getByName(host));
23 |
24 | System.err.println("Server listening on " + host + ":" + port + "\n");
25 | int read;
26 | byte[] buffer = new byte[8192];
27 |
28 | while(true) {
29 | Socket client = server.accept();
30 | System.out.println("Connection accepted from " + client.getRemoteSocketAddress());
31 | PrintWriter out = new PrintWriter(client.getOutputStream(), true);
32 | InputStream in = client.getInputStream();
33 |
34 | while((read = in.read(buffer)) > 0) {
35 | System.out.write(buffer, 0, read);
36 | }
37 |
38 | System.out.println("");
39 |
40 | out.write("HTTP/1.1 200 OK");
41 | out.close();
42 | in.close();
43 | client.close();
44 | }
45 | }
46 | finally {
47 | System.out.println("Closing");
48 | if(server != null)
49 | server.close();
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 | This repository contains reference Dockerfiles recommended to use inside the projects. We tried to capture best practices writing these files for production usage.
3 | There are general guidelines as well as language-specific reference files. The files are heavily documented to explain usage of particular command or option.
4 |
5 | ## General guidelines
6 | - Minimize number of layers in resulting image while still leveraging layer caching (https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers).
7 | - Avoid installing debugging software inside a resulting image (e.g., curl, ssh)
8 | - Combine shell commands with `&&`
9 | - Split long commands into multiple lines with `\`. Sort build dependencies alphabetically.
10 | - Compose Dockerfile such that less frequently changing commands come before more frequently changing ones.
11 | - Limit your container to a single responsibility. Run an app inside a container as PID 1 (https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#decouple-applications)
12 | - Use minimal base images (e.g., Alpine). Consider [differences](https://wiki.musl-libc.org/functional-differences-from-glibc.html) between `libc` vs `musl`.
13 | - Consider using multi-stage builds to minimize resulting image size (https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#use-multi-stage-builds)
14 | - Use `.dockerignore` files to avoid copying unnecessary files into a resulting image (https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#exclude-with-dockerignore)
15 | - Combine APT cache update and install commands into a single command to exploit "cache busting" technique (https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get):
16 | ```
17 | RUN apt-get update && \
18 | apt-get -y install --no-install-recommends curl
19 | ```
20 | - Use `EXPOSE` command to indicate what ports an application is listening on (https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#expose).
21 | - Run your application as non-root user (https://medium.com/better-programming/running-a-container-with-a-non-root-user-e35830d1f42a)
22 | - Use labels to add metadata to resulting image
23 |
24 | ## Python reference Dockerfile
25 | See [python/Dockerfile](python/Dockerfile)
26 |
27 | ## Java reference Dockerfile
28 | See [java/Dockerfile](java/Dockerfile)
29 |
--------------------------------------------------------------------------------
/java/Dockerfile:
--------------------------------------------------------------------------------
1 | # This is a Dockerfile for example Java app. It strives to capture best practices for writing Dockerfiles described in official
2 | # docs as well as some other non-official sources. It is heavily commented. You can grab a comments-free version of the file from
3 | # Dockerfile-prod-java. It is created upon `make` execution (See Makefile)
4 | FROM openjdk:8u232-jdk-slim-buster as build
5 |
6 | # Labels are key-value pairs that capture metadata of the image. Examples are image version, maintainer, etc
7 | # MAINTAINER instruction is deprecated. It is recommended to switch to LABEL
8 | # See: https://docs.docker.com/engine/reference/builder/#label
9 | LABEL maintainer="Artyom Bakhtin "
10 |
11 | # 1. Minimize number of layers by concatenating instructions with '&&'
12 | # 2. Use '\' to split long commands into multiple lines
13 | # 3. Always run apt-get update && apt-get install in a single layer to take advantage of 'cache busting' technique
14 | # See: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get
15 | # Otherwise, versions of packages will not be updated due to stale cache.
16 | # 4. No need to explicitly clean up APT cache. Debian-/Ubuntu-based images do it automatically (NOT REQUIRED: rm -rf /var/lib/apt/lists/*)
17 | RUN apt-get update && apt-get install -y --no-install-recommends \
18 | telnet
19 |
20 | # Copy source code into build stage
21 | COPY ./app /build
22 |
23 | # Switch to working directory with build. Avoid using 'cd' when possible
24 | WORKDIR /build
25 |
26 | # Compile and package into JAR. Replace it with whatever steps are required to build your app.
27 | RUN javac echoserver/Server.java && jar cfme app.jar META-INF/MANIFEST.MF echoserver.Server echoserver/Server.class
28 |
29 | # Second stage. We grab whatever artifacts produced in the previous stage and just copy it to the second stage which is a minimal environment without development tools (JRE vs JDK).
30 | FROM openjdk:8u232-jre-slim-buster
31 |
32 | # Create a separate unprivileged user to run an app inside a container. Using 'root' user inside a container to run an application is
33 | # strongly discouraged. Container's 'root' user 1:1 maps to 'root' user on a host system by default. Should there be any vulnerabilities
34 | # discovered allowing to escape Docker container an app could gain root privileges on a host system.
35 | RUN useradd --create-home --shell /bin/bash app
36 |
37 | # Switch to a user created above
38 | USER app
39 |
40 | # Copy artifacts from the previous build stage. Set 'app' user as owner
41 | COPY --from=build --chown=app:app /build/app.jar /app/
42 |
43 | # A port an application is accepting incoming connections on (if any). Purely for informational purposes but enables to quickly capture this
44 | # information by other maintainers.
45 | EXPOSE 8080
46 |
47 | # Switch to working directory with an app
48 | WORKDIR /app
49 |
50 | # Run application. See the difference between ENTRYPOINT vs CMD:
51 | # https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#cmd
52 | # Use /dev/urandom as a source of randomness since it is a non-blocking I/O device (rather than /dev/random) on Linux. The option can be omitted for JDK>=9. See: https://bugs.openjdk.java.net/browse/JDK-8143166.
53 | # /dev/urandom is as secure as /dev/random (See: https://www.2uo.de/myths-about-urandom)
54 | CMD ["java", "-Djava.security.egd=file:///dev/urandom", "-jar" , "app.jar"]
55 |
56 | # As of Java version 8u212+ you no longer need to explicitly set JVM memory options (namely, -XX:+UseCGroupMemoryLimitForHeapand -XX:+UnlockExperimentalVMOptions) to make JVM aware it is running inside a Docker container.
57 | # There are now 3 available parameters for JVM memory configuration:
58 | # - -XX:InitialRAMPercentage
59 | # - -XX:MaxRAMPercentage
60 | # - -XX:MinRAMPercentage
61 | # See SO answer explaining these options: https://stackoverflow.com/a/54297753
62 | # Also: https://blog.softwaremill.com/docker-support-in-new-java-8-finally-fd595df0ca54
--------------------------------------------------------------------------------
/python/Dockerfile:
--------------------------------------------------------------------------------
1 | # This is a Dockerfile for example Python web app. It strives to capture best practices for writing Dockerfiles described in official
2 | # docs as well as some other non-official sources. It is heavily commented. You can grab a comments-free version of the file from
3 | # Dockerfile-prod-python. It is created upon `make` execution (See Makefile)
4 |
5 | # This is a multistage build that uses python:3.7 as a named base image
6 | # See: https://docs.docker.com/develop/develop-images/multistage-build/
7 | FROM python:3.7 AS build
8 |
9 | # Labels are key-value pairs that capture metadata of the image. Examples are image version, maintainer, etc
10 | # MAINTAINER instruction is deprecated. It is recommended to switch to LABEL
11 | # See: https://docs.docker.com/engine/reference/builder/#label
12 | LABEL maintainer="Artyom Bakhtin "
13 |
14 | # 1. Minimize number of layers by concatenating instructions with '&&'
15 | # 2. Use '\' to split long commands into multiple lines
16 | # 3. Always run apt-get update && apt-get install in a single layer to take advantage of 'cache busting' technique
17 | # See: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get
18 | # Otherwise, versions of packages will not be updated due to stale cache.
19 | # 4. No need to explicitly clean up APT cache. Debian-/Ubuntu-based images do it automatically (NOT REQUIRED: rm -rf /var/lib/apt/lists/*)
20 | RUN apt-get update && apt-get install -y --no-install-recommends \
21 | telnet
22 |
23 | # Always pin versions of dependencies in requirements.txt
24 | # See requirements.txt for more info
25 | COPY ./requirements.txt ./requirements.txt
26 |
27 | # Install dependencies locally for a user. We can then easily copy a single directory from inside the following stage and use it similar
28 | # to virtualenv. Using a virtualenv is also an option but does not make much sense since there the only 'venv' in the resulting image.
29 | RUN pip3 install --user -r ./requirements.txt
30 |
31 | # Second stage. We can grab whatever artifacts produced in the previous stage. Python example is a bit artificial since no binaries are
32 | # built due to interpreted nature of Python. The example would have been more evident for compiled languages. In that case we could have
33 | # used first stage as environment for compilation. And then just copy resulting binaries to the second stage which is minimal environment
34 | # without development tools (such as gcc or cmake).
35 | FROM python:3.7
36 |
37 | # Create a separate unprivileged user to run an app inside a container. Using 'root' user inside a container to run an application is
38 | # strongly discouraged. Container's 'root' user 1:1 maps to 'root' user on a host system by default. Should there be any vulnerabilities
39 | # discovered allowing to escape Docker container an app could gain root privileges on a host system.
40 | RUN useradd --create-home --shell /bin/bash app
41 |
42 | # Switch to a user created above
43 | USER app
44 |
45 | # Copy artifacts from the previous build stage. Set 'app' user as owner
46 | COPY --from=build --chown=app:app /root/.local /home/app/.local
47 |
48 | # Copy application files. Set 'app' user as owner. Note that the entire application is stored in a single directory. That is to avoid
49 | # enumerating to-be-copied files in COPY command one by one or composing verbose .dockerignore file.
50 | COPY --chown=app:app app /app
51 |
52 | # A port an application is accepting incoming connections on (if any). Purely for informational purposes but enables to quickly capture this
53 | # information by other maintainers.
54 | EXPOSE 8080
55 |
56 | # Add path with python packages to existing PATH environment variable.
57 | ENV PATH=/home/app/.local/bin:$PATH
58 |
59 | # Force the stdout and stderr streams to be unbuffered. This is useful for receiving timely log messages and avoiding situations where the application crashes without emitting a relevant message due to the message being "stuck" in a buffer.
60 | # Source: https://github.com/awslabs/amazon-sagemaker-examples/issues/319#issuecomment-405749895
61 | ENV PYTHONUNBUFFERED=1
62 |
63 | # Switch to working directory with an app. Avoid using 'cd' when possible
64 | WORKDIR /app
65 |
66 | # Run application. See the difference between ENTRYPOINT vs CMD:
67 | # https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#cmd
68 | CMD ["python3", "main.py"]
69 |
--------------------------------------------------------------------------------