.. index:: pair: Docker ; Best practices ! Docker Best practices .. _docker_best_practices_nick: ============================================= Docker Best practices from Nick Janetakis ============================================= .. seealso:: - :ref:`nick_janetakis` - https://nickjanetakis.com/blog/best-practices-when-it-comes-to-writing-docker-related-files - https://github.com/nickjj/docker-web-framework-examples - https://dev.to/nickjj/best-practices-when-it-comes-to-writing-docker-related-files-ek3 - https://github.com/nickjj/docker-web-framework-examples .. contents:: :depth: 3 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 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: - Flask - Node / Express - Phoenix - Rails - Webpack 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. Flask example ============== .. seealso:: - https://github.com/nickjj/docker-web-framework-examples/tree/master/flask Flask Dockerfile ------------------ :: FROM python:2.7-alpine LABEL maintainer="Nick Janetakis " # 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 --------------------------- .. code-block:: yaml 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 ----------- .. seealso:: - https://www.gitignore.io/api :: # 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