├── .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 | --------------------------------------------------------------------------------