=============================================================================== Docker File Notes NOTE: During the 'build' the new 'buildkit' process will not show the output of the commands that are run unless something goes wrong. You get it to show the output of command run (not already successfully cached) either add the option --progress plain or set ten environment variable... export BUILDKIT_PROGRESS=plain =============================================================================== # # BASIC: ubuntu xenial machine # FROM ubuntu:xenial # update and add packages (ubuntu) RUN apt-get update RUN apt-get install -y openssh-server RUN useradd -ms /bin/bash user USER user =============================================================================== Base Images... For example FROM: python-3.9.3:rc-buster FROM: python-3.9.3:alpine Major difference is size... buster 934MB slim 165MB alpine 79MB ubuntu ??? centos ??? https://www.youtube.com/watch?v=e2pAkcqYCG8 WARNING: do not ever use :latest in a production Dockerfile As appliaction dependancys may not work in newer versions. It is also harder to upgrade, AND test the upgrade! Buster Is a fill linux of debian (Debian versions: v8 Jessie v9 Stretch v10 Buster v11 Bullseye) * The buster image is 20 times LARGER than alpine Buster-Slim Paired down version of the full image * Minimal packages needed =======8<--------CUT HERE---------- FROM "debian:buster-slim" RUN set -eux; \ \ : "System changes"; \ ln -sf /usr/share/zoneinfo/Australia/Brisbane /etc/localtime; \ \ : "SSH File Transfer Software"; \ apt-get update && apt-get install -y --no-install-recommends \ openssh-client openssh-sftp-server rsync; \ mkdir -p /usr/libexec/openssh; \ ln -s /usr/lib/sftp-server /usr/libexec/openssh/sftp-server; \ \ : "User changes to allow SSH"; \ chsh --shell /bin/bash www-data; \ \ : "Working Directory Mount Point"; \ install -d -m 755 -o www-data -g www-data /var/www; \ \ : "Clean Apt system" ;\ apt-get clean; \ rm -rf /var/lib/apt/lists/*; \ rm -rf /tmp/* /var/tmp/* ; \ rm -f /var/log/{apt/*,alternatives.log,dpkg.log}; \ :; Alpine From the Alpine Linux Project, specifically for use inside of containers https://alpinelinux.org/ To install packages you use "apk" instead of "apt-get" =======8<--------CUT HERE---------- RUN set -eux; \ apk update \ apk add python3 tzdata \ ln -fs /usr/share/zoneinfo/Australia/Brisbane /etc/localtime \ echo "Australia/Brisbane" > /etc/timezone \ rm -rf /var/cache/apk/* =======8<--------CUT HERE---------- However applications need to be checked thoughly. And CODE must be specifically compiled for alpine. * Base image (without app) is 5MB with python 45MB (very small) * Only bourne shell.. docker exec -it --user root {container} /bin/sh * alpine does not have a defailt PATH, So "ENTRYPOINT" must give full path to the binary/script to run * Uses a slimmer libc instead of glibc, which is a primary source of bugs and differences. * Some packages you have to build yourself and have problems. Resulting in image sizes on par with 'slim' WindowsServerCore for running windows packages =============================================================================== Set up complex environment (Lots a commands) This method creates a BIG Dockerfile run script Use the bash options "set -eux" so it aborts on any error, while reporting what command is being run for the output that follows. The ":" bash 'noop' command allowa use to include 'comments' in the script which is reported in in the docker build output. The use of 'globs' in the final cleanup command, will create a very BIG command in docker build output. Switching to replacing "set -eux" with set -euv" can avoid this. =======8<--------CUT HERE---------- FROM "php:7.3-apache" RUN set -eux; \ : Install extra packages ; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates \ dirmngr \ gnupg \ wget \ ;\ : "System changes"; \ ln -sf /usr/share/zoneinfo/Australia/Brisbane /etc/localtime; \ mkdir -p /usr/libexec/openssh; \ ln -s /usr/lib/sftp-server /usr/libexec/openssh/sftp-server; \ :\ : "--- user changes ---" \ :;\ chsh --shell /bin/bash www-data; \ chown -R www-data:www-data /var/www; \ :\ : "--- cleanup ---" \ :;\ apt-get clean; \ : DISABLED 'rm -rf /var/lib/apt/lists/*'; \ rm -rf /tmp/* /var/tmp/* /var/log/lastlog /var/log/faillog; \ : =======8<--------CUT HERE---------- =============================================================================== Specific Dockerfile techniques... ------------------------------------------------------------------------------- Create small text file from dockerfile Using individual commands =======8<--------CUT HERE---------- RUN set -eux; \ cd $PHP_INI_DIR/conf.d/; \ echo "memory_limit=2048M" > memory-limit.ini; \ echo "max_execution_time=900" >> memory-limit.ini; \ echo "post_max_size=20M" >> memory-limit.ini; \ echo "upload_max_filesize=20M" >> memory-limit.ini; \ : =======8<--------CUT HERE---------- Or Group the output of the commands... =======8<--------CUT HERE---------- RUN set -eux; \ { echo 'Package: php*'; \ echo 'Pin: release *'; \ echo 'Pin-Priority: -1'; \ } > /etc/apt/preferences.d/no-debian-php =======8<--------CUT HERE---------- Load it from a separate file (relative to the pwd) =======8<--------CUT HERE---------- WORKDIR /var/www COPY image_files/bashrc .bashrc =======8<--------CUT HERE---------- Extract from a large archive (from "debian:stretch-slim") =======8<--------CUT HERE---------- FROM scratch ADD rootfs.tar.xz /app CMD ["bash"] =======8<--------CUT HERE---------- =============================================================================== Non-privileged User =======8<--------CUT HERE---------- # USER add a non-privileged user "user" # From this point ALL commands 'RUN' or "COMMAND" will be run as this user! RUN useradd -ms /bin/bash user USER user =======8<--------CUT HERE---------- =============================================================================== Entry Point that does nothing All activity in the container is done using remote 'docker exec' commands. =======8<--------CUT HERE---------- ENTRYPOINT [ "tail", "-f", "/dev/null" ] =======8<--------CUT HERE---------- =============================================================================== Install gosu ENV GOSU_VERSION 1.11 RUN set -eux; \ : 'download gosu.; \ dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \ \ : 'verify the signature'; \ export GNUPGHOME="$(mktemp -d)"; \ gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ gpgconf --kill all; \ rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ \ : 'verify that the binary works'; \ gosu --version; \ gosu nobody true; \ : =============================================================================== Download Apts to use, then delete them and dependancies. =======8<--------CUT HERE---------- RUN set -eux; \ : 'save list of currently installed packages for later' \ savedAptMark="$(apt-mark showmanual)"; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates \ dirmngr \ gnupg \ wget \ ; \ rm -rf /var/lib/apt/lists/*; \ : \ : '...DO WHATEVER IS NEEEDED WITH INSTALLED APPS...' \ :; \ : 'cup fetch dependencies'; \ apt-mark auto '.*' > /dev/null; \ [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \ apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ : =======8<--------CUT HERE---------- =============================================================================== Multiple services in on Docker Continer https://docs.docker.com/config/containers/multi-service_container/ * Script starts services, and monitors them, exiting when one dies * Use a script to start services, ensuring the script exits on one of them * Use spervisorD to start services, and monitor them to restart as needed =============================================================================== Using Supervisor to run N wrokers The main manual http://supervisord.org/ Supervisor can be installed using pip install supervisor OR apt-get install -y supervisor Example pthon docker to run workers =======8<--------CUT HERE---------- FROM python:3.9.2-alpine # Install the "worker", and supervisor.conf to /app WORKDIR /app COPY ./ ./ # set up environment RUN set -eux; \ : 'Install Python Dependancies'; \ pip install --no-cache-dir supervisor; \ pip install --no-cache-dir -r worker_requirements.txt; \ : # No nothing entry, use "docker exec" #ENTRYPOINT [ "tail", "-f", "/dev/null" ] # run one worker, when it dies container dies #ENTRYPOINT ["/app/worker"] # supervisord (using config in current directory), to keep N workers running. ENTRYPOINT ["/usr/local/bin/supervisord"] =======8<--------CUT HERE---------- You can get a example supervisor.conf using /usr/bin/echo_supervisord_conf The supervisord.conf found either in current directory, Or from /etc/supervisord.conf or defined using '-c 'config}' Example Supervisor.conf file to keep run N workers running (unless they kee dying) NOTE the sworker processes should not deamonize themselves. =======8<--------CUT HERE---------- [supervisord] ; run in foregroud, not as daemon (equivent to '-n') nodaemon=true ; Log Level default=info, ; to include sub-process output "debug" is needed. loglevel=debug ; where worker output logs are stored ; filesnames are of the form "worker-0-stdout---supervisor-0ll9j_vo.log" ;childlogdir=%(here)s ; set log file to /dev/null, and disable log rotation ; automatically goes to stdout (with logs) if "nodeamon=true" ; so setting this to /dev/stdout doubles up the output! logfile=/dev/null logfile_maxbytes = 0 ; disable the log to stdout (use logfile only) ; silent=true ; Directory supervisor runs in ; directory=/app ' Environment to share with sub-processes environment=PYTHONUNBUFFERED=true ; run as root without warning (or switch to user) user=root ; ------------------------------------------- [program:workers] ; Point the command to run a sub-process command=/app/worker ; process_num is required if you specify >1 numprocs process_name=worker-%(process_num)s ; how many subprocess processes? numprocs=1 ; No need log files - output is on supervisors output stdout_logfile=/dev/null stdout_logfile_maxbytes=0 redirect_stderr=true ; Directory subprocess runs in ; directory=/app ; How to nicely kill sub-process for shutdown stopsignal=TERM ; Wait this long (sec) after stop for program to exit (default=10) ; if longer than this a 'KILL' signal is then sent ;stopwaitsecs=10 ; Keep sub-processes running autostart=true autorestart=true ; program needs to run this long (secs) to be sussedful (default=1) ; startsecs=5 ; how many retries to start (backs off by 1 second each attempt) startretries=3 =======8<--------CUT HERE---------- Note supervisor logs to stdout (docker log) and current directory (supervisor.log or "-l {logfile}") IT does rotation of its own log files ("-y {size} -z {num}") ===============================================================================