DockerFile Annotated Example

Lessons & Guidelines Learned

# syntax = docker/dockerfile:experimental
# Safer with 1.0: syntax = docker/dockerfile:1.0-experimental
# Syntax directive must be first line
# See: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
#################################
# Master Wapiti Docker Build File
# This file builds testable & deployable commercial containers
# TODO
# Comply with https://www.opencontainers.org over time
# Set Capabiltiies
# See https://offby2.com/posts/001-docker-lesser-known-tips/
# Add HEALTHCHECK process to mark unhealthy
# Add STOPSIGNAL
# Add user & run as non-root
# See: https://github.com/zabbix/zabbix-docker/blob/5.0/agent/alpine/Dockerfile
# Build-time args go here before FROM, used via --build-arg <varname>=<value>
# See: https://docs.docker.com/engine/reference/builder/#arg
# Note Docker defines a few of these
# Buildkit adds several more regarding platforms
# To use these, they must be added AFTER the FROM
# Note changed ARG value invalidates cache from declare point
ARG BUILD_ENV
# Based on official PHP container
# Note below on assumptions from that base
# Use 7.3 for now as no mod_php yet via php7-apache2 on Alpine
FROM php:7.3.16-alpine
# Global Args from Docker/BuildKit must be added here after FROMARG TARGETPLATFORM
# ONBUILD used to run things in downstream builds
# Such as single layer copies
# Not used for now
# ONBUILD
# OCI Annotations from https://github.com/opencontainers/image-spec/blob/master/annotations.mdLABEL org.opencontainers.image.maintainer="Steve.Mushero@ELKman.io"     \      org.opencontainers.image.authors="Steve.Mushero@ELKman.io"        \      org.opencontainers.image.title="ELKman"                           \      org.opencontainers.image.vendor="Wapiti Systms Inc."              \      org.opencontainers.image.url="https://www.elkman.io"              \      org.opencontainers.image.documentation="https://elkman.io"
\ org.opencontainers.image.description="ELK Management"
\ # SPDX License Expression format;
\ # UNLICENSED is used for private licenses
\ org.opencontainers.image.licenses="UNLICENSED" \ org.opencontainers.image.version="${ELKMAN_VERSION}"
\ org.opencontainers.image.version="RELEASE"

# org.opencontainers.image.revision="" FROM git
# org.opencontainers.image.created="2020-05-01T01:01:01.01Z"
# Offical PHP Apache container defaults & assumptions
# From base Dockerfile:
# User: www-data
# WORKDIR: /var/www/html (Note we change this)
# php.ini: In /usr/local/etc/php (Note we update this via sed)
# Apache Conf: In /etc/apache2 (Note we update this via sed)
# Packages: LOTS of dev like gcc, g++, make, etc. probably remove
# Get Secrets - Later using Buildkit system
# RUN --mount=type=secret,id=mysecret cat /deploy/.secrets
ENV MAINWORKDIR /var/www
ENV MAINUSER root
ENV MAINGROUP root
ENV APACHEUSER www-data
ENV APACHEGROUP www-data
# Lists of tools - will shrink over time to reduce size
# Alpha order, please
# Telnet not available on alpine
# Need coreutils, iputils?
ENV INSTALL_TOOLS \
bash \
busybox-extras \
curl \
less
# Tried using openrc for rc-service, but can't really run in container as needs /sys/fs/cgroup access
# If we need multi-process support, will find something light; supervisor needs Python
# apk supports --virtual to group & later remove packages
# RUN apk add --no-cache --virtual .build-deps gcc
# RUN apk del --no-cache .build-deps
# Update Repo Info & Install Basic PackagesRUN apk update --no-cache && apk add --no-cache --clean-protected ${INSTALL_TOOLS}
# Install Specialized Packages
# We need SQLite for Telescope & other uses
# Note libsqlite3-dev is in base image
# ENV EXTRA_PACKAGES sqlite3
RUN apk update --no-cache && apk add --no-cache ${EXTRA_PACKAGES}
# Stuff to remove for smaller size
# Packages: Some images have dev stuf like gcc, g++, make, etc.
# ENV REMOVE_PACKAGES xx
# RUN apk del $(REMOVE_PACKAGES)
##### End of OS Items #####
##### Apache Items #####

# Install Apache & PHP Modules
# php7-apache2 installs much of PHP & Apache,
ENV PHP_PACKAGES php7 php7-apache2 php7-json php7-phar php7-iconv \
php7-openssl php7-curl php7-mbstring php7-fileinfo \
php7-tokenizer php7-dom php7-session php7-pdo php7-pdo_sqlite \
php7-xml php7-simplexml php7-xmlwriter php7-zip
RUN apk update --no-cache && apk add --no-cache --clean-protected ${PHP_PACKAGES}
# Using default Alpine Apache configs and modifying from there
# Then we override, which lets us use unmodified official files
ENV APACHECONFFILE /etc/apache2/httpd.conf
ENV APACHECONFDDIR /etc/apache2/conf.d
ENV APACHEVHOSTCONFFILE ${APACHECONFDDIR}/default.conf
ENV APACHESECURITYFILE ${APACHECONFDDIR}/security.conf
# Copy over PHP file from PHP-Apache
# Skipping as seems the Alpine version has one: php7-module.conf
# COPY /deploy/apache/docker-php.conf ${APACHECONFDDIR}/docker-php.conf
RUN echo && \
# Remove stuff we don't want nor need for security, etc.
rm /etc/apache2/conf.d/userdir.conf && \
rm /etc/apache2/conf.d/info.conf && \
#
# Apache main config overrides
#
sed -ri -e 's/^#ServerName.*$/ServerName elkman/g' ${APACHECONFFILE} && \
sed -ri -e 's/^ServerTokens.*$/ServerTokens Prod/g' ${APACHECONFFILE} && \
sed -ri -e 's/^ServerSignature.*$/ServerSignature Off/g' ${APACHECONFFILE} && \
#
# Need to allow .htaccess overrides for Laravel, it rewrites paths to use index.php
#
sed -ri -e 's/AllowOverride None.*$/AllowOverride All/g' ${APACHECONFFILE} && \
sed -ri -e 's/\/var\/www\/localhost\/htdocs/\/var\/www\/public/g' ${APACHECONFFILE} && \
sed -ri -e 's/^#LoadModule rewrite_module modules\/mod_rewrite.so/LoadModule rewrite_module modules\/mod_rewrite.so/g' ${APACHECONFFILE} && \
#
# Security Overrides (creating new file)
#
echo 'Header set X-Content-Type-Options: "nosniff" ' > ${APACHESECURITYFILE} && \
echo 'Header set X-Frame-Options: "sameorigin" ' >> ${APACHESECURITYFILE}
##### PHP Items #####

# PHP Configs - Complicated as there are two PHP on Alpine 7.3 container
# Some PHP containers use date-specific extension dir by default in php.ini
# On Alpine, careful of which php is used for CLI vs. mod_php to verify their paths

# Disble default php in path so can't get confused over configs, modules, etc.
# Then the one we want works fine in path
RUN mv /usr/local/bin/php /usr/local/bin/php.bad
# For Alphine 7.3 we use /usr/bin/php and /usr/etc/phpENV PHP_INI_DIR /etc/php7
ENV PHPEXTDIR "/usr/lib/php7/modules/"
# Use the default prod configuration from php:7.4.4-apache (php.ini-development also exists)
COPY deploy/php/php.ini-production $PHP_INI_DIR/php.ini
# Copy overrides
COPY deploy/php/php-override-prod.ini $PHP_INI_DIR/conf.d/
COPY deploy/php/php-sourceguardian.ini $PHP_INI_DIR/conf.d/
# Copy Source Guardian run-time Loader
COPY deploy/php/ixed.7.3.lin ${PHPEXTDIR}

# Install composer & prestissimo for parallel downloads if needed
RUN curl -sS https://getcomposer.org/installer | \
php -- --install-dir=/usr/local/bin --filename=composer && \
composer global require hirak/prestissimo --no-plugins --no-scripts
# Install NPMRUN apk update --no-cache && apk add --no-cache --clean-protected npm
#### Add Code ####

# Need to change WORKDIR as Apache default is /var/www/html
WORKDIR ${MAINWORKDIR}

# Copy files from VM
# Copy App Directories - Not setting owners here, it's done later
# Note will ignore the .dockerignore things, so tune that, too
# Currently we depend on git to create/ignore all the dirs we need, especially in storage
# We do this because later we want to git clone into container as part of build
COPY app app
COPY config config
COPY resources resources
COPY routes routes
COPY bootstrap bootstrap
COPY database database
COPY storage storage
COPY public public
COPY tests tests
# Copy Specific Files
COPY artisan ./
COPY composer.json ./
COPY composer.lock ./
COPY package.json ./
COPY package-lock.json ./
COPY webpack.mix.js ./
# Run Composer install#   ENV COMPOSER_CACHE_DIR - Can set if needed, now using default#   Cannot use RUN mount here as we need a cache dir, and mount only supports files (as far as I can tell)#   Copy in composer cache, use and remove

COPY /composer-cache/files /root/.composer/cache/files
# Note: Have to run 'composer dump-autoload' for some reason here; seems install not fully doing itRUN composer install --no-dev --classmap-authoritative --no-ansi \
--no-scripts --no-interaction --no-suggest && \
composer dump-autoload && \
rm -rf /root/.composer/cache
# NPM Stuff & Webpack (part of dev script)
# RUN npm install --no-optional
# Moving to ci instead of install (ci uses lock file)
RUN npm ci --no-optional
RUN npm run prod
# Move public artifacts to doc root - do this after npm run
# Get .htaccess, too
# We missing anything in the standard html?
# Not moving as better to point Doc Root to our public
# RUN mv public/* html/ && mv public/.htaccess html/
# Move DB file from source tree to writable storage area
# For now, touch empty file; likely copy real DB later
# RUN mv database/db.sqlite storage/database/
RUN touch storage/database/db.sqlite
# .env File - Need to copy for productionCOPY .env.production .env

# Copy dusk env for now for testing
COPY .env.dusk.testing .env.dusk.testing
# Setup configs & code; may later do as other user, fixed UID, etc.# Generate a new key each time (though we also need on install)RUN php artisan key:generate# Optimize & cache; do before we migrate or run other artisan jobsRUN php artisan optimize# Seed tables, Telescope, etc. data into DB
# Run after keygen, before other artisan cmds
RUN php artisan migrate# Update DB version to app code version; this for container's initial DB onlyRUN php artisan elkman:update
# Remove log file so we start clean (and with right log file owner)
RUN rm -rf storage/logs/*
# Permissions carefully managed here
# Set all directory permissions
# Set global owner & read perms, then set for writable, exec, etc.
ENV READPERM 440
ENV WRITEPERM 660
RUN chown -R ${MAINUSER}:${APACHEGROUP} ./ && \
chmod -R ${READPERM} ./ && \
chmod -R ${WRITEPERM} storage && \
# Set all dirs to be executable so we can get into them
# Do after any chmods above
find ./ -type d -print0 | xargs -0 chmod ug+x
### Data Purge
# Need to purge & cleanup
# rm composer & caches
# rm npx & caches
# rm any man pages, etc.
# vendor cleanup

RUN rm -rf /tmp/*

# End of apk installs, we can clean (apk cache clean seems to not do antyhing) - saves little
RUN rm -rf /var/cache/apk/*
#### Docker Run Time Items ####

# Offical PHP Apache container defaults & assumptions, from base
# EXPOSE 80
# ENTRYPOINT ["docker-php-entrypoint"]
# CMD ["apache2-foreground"]

# TODO: Set USER ?

# Override base container port to specify TCP
# And make it easier to change
EXPOSE 80/TCP

# Careful to separate each argument, e.g. like "-f" and the file:
CMD ["/usr/sbin/httpd", "-f", "/etc/apache2/httpd.conf", "-DFOREGROUND"]

# Run command:
# docker run -d -p 8000:80 elkmanio/elkman:latest

CEO of ChinaNetCloud & Siglos.io — Global Entrepreneur in Shanghai & Silicon Valley

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store