Docker Best practices from Nick Janetakis

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 not 2-alpine

  • Add a maintainer LABEL to keep tabs on who initially made the image

  • Only include ARG and ENV instructions if you really need them

  • Use /app to store your app’s code and set it as the WORKDIR (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 driven HEALTHCHECK (if it makes sense)

  • Stick to the [] syntax when supplying your CMD instructions

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 not 10-alpine

  • Use . instead of $PWD for when you need the current directory’s path

  • Prefer build: “.” unless you need to use args or some other sub-property

  • If your service is a web service, publish port 8000 unless it doesn’t make sense to

.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

Flask example

Flask Dockerfile

FROM python:2.7-alpine
LABEL maintainer="Nick Janetakis <nick.janetakis@gmail.com>"

# If you plan to use PostgreSQL then you must add this package: postgresql-dev.
RUN apk update && apk add build-base

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000
HEALTHCHECK CMD wget -q -O /dev/null http://localhost:8000/healthy || exit 1

CMD ["gunicorn", "-c", "python:config.gunicorn", "hello.app:create_app()"]

.env file

COMPOSE_PROJECT_NAME=flaskhello
PYTHONUNBUFFERED=true

Flask docker-compose.yml

version: "3.6"

services:
  web:
    build: "."
    command: >
      gunicorn --reload -c "python:config.gunicorn" "hello.app:create_app()"
    env_file:
      - ".env"
    ports:
      - "8000:8000"
    volumes:
      - ".:/app"

hello/app.py

from flask import Flask
from werkzeug.debug import DebuggedApplication


def create_app(settings_override=None):
    """
    Create a Flask application using the app factory pattern.

    :param settings_override: Override settings
    :return: Flask app
    """
    app = Flask(__name__, instance_relative_config=True)

    app.config.from_object('config.settings')
    app.config.from_pyfile('settings.py', silent=True)

    if settings_override:
        app.config.update(settings_override)

    if app.debug:
        app.wsgi_app = DebuggedApplication(app.wsgi_app, evalex=True)

    @app.route('/')
    def index():
        return 'Hello world with DEBUG={0}'.format(app.config['DEBUG'])

    @app.route('/healthy')
    def healthy():
        return ''

    return app

.gitignore

# Created by https://www.gitignore.io/api/python,osx

### OSX ###
*.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/

# Translations
*.mo
*.pot

# Flask stuff:
instance/settings.py
.webassets-cache

# Scrapy stuff:
.scrapy

# celery beat schedule file
celerybeat-schedule.*

# End of https://www.gitignore.io/api/python,osx