Table of Contents
Docker
Docker is similar to LXC, however it is geared to running a single application within its jailed system rather than being a lightweight VM. It's like having a chroot management system enhanced with networking. One side effect of not having an init process is zombie processes can accumulate in the container. Docker is also dependant on the overlay2 file system which is relatively slow.
Docker has a lot of pre-built applications, and due to this the docker ecosystem is a security nightmare and cannot really be recommended unless you skip this and create your own builds from base O/S images such as Alpine Linux.
On this page are demos of using Docker on Linux. One container is an install of Laravel and another is Alpine Linux. The installation of Laravel should demonstrate the security issues when using pre-built docker images.
Alpine Linux is further configured for PHP8, composer and Symfony. This is not for production, just for development and not fully configured here, yet.
Later a build is investigated containing WordPress using Alpine Linux and accessed via a proxy with nginx.
Environment
VirtualBox virtual machine manager or Xen virtual machine.
Operating System
Devuan Chimaera on VirtualBox or Debian Bullseye on Xen.
Docker
Install
apt-get install apparmor apt-get install docker.io apt-get install docker-compose
User
Add your login user to the docker group with vigr and vigr -s and relog.
Run
/etc/init.d/docker start
Laravel
wget "https://laravel.build/example-app" -O example-app.sh sh ./example-app.sh cd example-app ./vendor/bin/sail up
Alpine Linux
Alpine Linux is a lightweight Linux ideal for running inside a container.
Install
Fetch the image and create the container.
docker pull alpine:latest docker create -t -i --name alpine_linux alpine:latest
Start
Start Alpine Linux.
docker start alpine_linux
Shell
Connect to a shell within Alpine Linux.
docker exec -it alpine_linux /bin/sh
Stop
Stop Alpine Linux
docker stop alpine_linux
Delete
How do delete Alpine Linux if necessary.
docker rm alpine_linux
PHP8
Install
docker exec -it alpine_linux /bin/sh apk update apk upgrade apk add php8 php8-fpm php8-opcache php8-cli php8-ctype php8-iconv php8-session php8-simplexml php8-tokenizer php8-openssl php8-phar ln -sf /usr/bin/php8 /usr/local/bin/php exit
Composer
Git
docker exec -it alpine_linux apk add git
Install
Visit the composer downloads page to find the composer installer script.
Save the script as composer-installer.sh
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');"
Run the saved script.
sh composer-installer.sh Installer verified All settings correct for using Composer Downloading... Composer (version 2.3.5) successfully installed to: /root/composer.phar Use it: php composer.phar
Move the blob into your path.
mv composer.phar /usr/local/bin/
Symfony
Install
Fetch the symfony blob.
wget "https://github.com/symfony-cli/symfony-cli/releases/download/v5.4.8/symfony-cli_linux_amd64.tar.gz" tar zxvf symfony-cli_linux_amd64.tar.gz
Move the blob into your path.
mv symfony /usr/local/bin/
Application
Create a symfony application.
symfony new --webapp my_project
Fixed IP address
Bridge
Create a network bridge using docker.
docker network create --subnet=10.44.0.0/24 vlan
Run
Create and run the container. The option d starts the container in the background and t assigns a pseudo tty.
docker pull alpine:latest docker run -d -t --net vlan --ip 10.44.0.10 --name alpine_linux alpine
Command
Run a command in the container
docker exec -it alpine_linux /sbin/ifconfig eth0 eth0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX inet addr:10.44.0.10 Bcast:10.44.0.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:10 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:876 (876.0 B) TX bytes:0 (0.0 B)
Stop
Stop container
docker stop alpine_linux
Restart
Restart container
docker start alpine_linux
WordPress development
This is the process I used to develop a WordPress docker container. Following this is the method using a Dockerfile which automates the process once it is known. Some differences are also found in the application of the container during development.
Proxy
Install nginx in the main host.
apt install nginx-full php php-cli php-fpm
Use proxy_pass to direct a https URL to the container.
Eg.
location / { proxy_pass http://x.x.x.x:80; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Port 443; proxy_buffering off; }
Apache2
Install apache2 in the container.
Install
docker exec -it alpine_linux sh apk update apk upgrade apk add php8 php8-apache2 php8-mysqli php8-mysqlnd php8-opcache php8-cli php8-ctype php8-iconv php8-session php8-simplexml php8-tokenizer php8-openssl php8-phar php8-curl php8-dom php8-exif php8-fileinfo php8-pecl-imagick php8-mbstring php8-zip php8-gd php-intl ln -sf /usr/bin/php8 /usr/local/bin/php exit
Config
At the least edit the Listen and ServerName directives in /etc/apache2/httpd.conf
Listen *:80 ServerName WordPress ServerAdmin root ServerTokens Prod ServerSignature Off
Logging
Use mod remoteip resolve client ip address.
Newer apache2
RemoteIPProxyProtocol On RemoteIPProxyProtocolExceptions 127.0.0.1 X.X.X.X/24 RemoteIPHeader X-Forwarded-For RemoteIPTrustedProxy X.X.X.X
Older apache2
RemoteIPHeader X-Real-IP RemoteIPTrustedProxy X.X.X.X LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
Run
Start apache2 in the container.
docker exec -it alpine_linux /usr/sbin/httpd -DFOREGROUND
To keep this process up, start it from RUNIT or similar in the main docker host.
run
/usr/bin/docker start alpine_linux exec /usr/bin/docker exec -e TZ=UTC -e PHP_INI_SCAN_DIR=/etc/php8/conf.d -t alpine_linux /usr/sbin/httpd -DFOREGROUND
finish
/usr/bin/docker stop alpine_linux
WordPress
Install WordPress in the container.
docker exec -it alpine_linux sh cd /var/www/localhost/htdocs wget "https://wordpress.org/latest.zip" unzip latest.zip rm latest.zip chown -R apache:apache wordpress exit
The URL will become https://example.com/wordpress/ and the setup URL will be https://example.com/wordpress/wp-admin/setup-config.php
If a MySQL server is not available then MySQL must be started in another container in the same network.
Add custom config values to support proxy.
define('FORCE_SSL_ADMIN', true); // in some setups HTTP_X_FORWARDED_PROTO might contain // a comma-separated list e.g. http,https // so check for https existence if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) $_SERVER['HTTPS']='on'; define('FS_METHOD','direct');
WordPress Dockerfile
A docker file can be used to automate the building of an image after one has been developed.
Here is a simple Makefile just used to contain various rules.
clean: rm -f *~ build: docker build --no-cache -t alpine_wp . run: docker run -d -t --net vlan --ip 10.44.0.11 --name wp-dev alpine_wp test: curl -v http://10.44.0.11/ stop: docker stop wp-dev rm: docker rm -f wp-dev docker image rm -f alpine_wp
The Makefile uses a Dockerfile to make the build, as follows:
# LATEST WORDPRESS ON ALPINE LINUX FROM alpine:latest # INSTALL APACHE2/PHP8 RUN apk --no-cache update && apk --no-cache upgrade && apk --no-cache add php8 php8-apache2 php8-mysqli php8-mysqlnd php8-opcache php8-cli php8-ctype php8-iconv php8-session php8-simplexml php8-tokenizer php8-openssl php8-phar php8-curl php8-dom php8-exif php8-fileinfo php8-pecl-imagick php8-mbstring php8-zip php8-gd php-intl && ln -sf /usr/bin/php8 /usr/local/bin/php # INSTALL WORDPRESS WORKDIR /var/www/localhost/htdocs RUN wget -q "https://wordpress.org/latest.zip" && unzip -q latest.zip && rm latest.zip && chown -R apache:apache wordpress && rm -f index.html && echo "<?php header('Location: /wordpress/')?>" > index.php # START HTTPD EXPOSE 80 ENTRYPOINT /usr/sbin/httpd -DFOREGROUND
Miscellaneous
Copy file
docker cp .vimrc alpine_linux:/root/.vimrc
Apache log rotation
#! /bin/sh # Apache log rotation. # # Dmb May 1999 - Jun 2022. # Alpine Linux: # # apk add apache2-utils webalizer # # 0 1 * * * /usr/bin/docker exec alpine_linux /root/rotatelog 1>/dev/null 2>/dev/null umask 022 LOGDIR="/var/log/apache2" WEBLOG="access.log" LOGFILES="$WEBLOG error.log" STATDIR="stats" cd $LOGDIR for f in $LOGFILES do if test -f $f; then test -f $f.6.gz && mv $f.6.gz $f.7.gz test -f $f.5.gz && mv $f.5.gz $f.6.gz test -f $f.4.gz && mv $f.4.gz $f.5.gz test -f $f.3.gz && mv $f.3.gz $f.4.gz test -f $f.2.gz && mv $f.2.gz $f.3.gz test -f $f.1.gz && mv $f.1.gz $f.2.gz test -f $f.0 && mv $f.0 $f.1 && gzip $f.1 cp -p $f $f.0 cat /dev/null >$f fi done if test -f $WEBLOG.0; then TMPLOG=`mktemp /tmp/logresolve.XXXXXXXXXX` logresolve < $WEBLOG.0 > $TMPLOG mkdir -p $STATDIR webalizer -Q -p -n"localhost" -o$STATDIR $TMPLOG rm -f $TMPLOG fi exit 0
Clone
First stop the container to commit any changes
The commit creates a tagged version of the container which has any changes made since installation.
docker stop alpine_lunux docker commit alpine_linux wordpress:v1
Container can be restarted now
docker start alpine_linux
Save the docker container as an image
This saves the container at the specified commit tag for import elsewhere.
docker save -o wordpress_v1.tar wordpress:v1
Container image can now be used elsewhere.