Creating a Docker development LEMP container

Colin Bitterfield
15 min readNov 29, 2019
logo by Anna Maria Paliasna Weaver

[ Updated for Ubuntu 22.04 and PHP 8.1 ]

I updated the build image and documentation for Ubuntu 22.04, PHP 8.1 and a few minor fixes.

Over the last week, I decided to create a LEMP implementation with DOCKER to support a PHP web app that I am writing. It started when I could not find a good image in the repository that had enough features. Most of the images are little more than a docker file that gets a web server working with “It Works..”. This is not a very useful image. I was able to duplicate that work in a couple of hours.

I learned several lessons along this discovery road that I would like to share. Let me enumerate the lessons.

The final project is available for review on GitHub.

Lesson 1: Have a good specification for what you are creating in the beginning. For creating a good LEMP environment, my specification needed more than a website.

Requirements:

  1. LEMP base software installed and integrated
  2. Support for TLS. I need to be able to debug against TLS enabled sites and an automatic redirect from HTTP to HTTPS.
  3. Named website support. To test the code, I need to be able to have different domain names and test against the production domain.
  4. Data persistence either to a Docker Volume or to a Host Volume.
  5. Ability to configure database names, database username, database password, which is separate from the database root password.
  6. Ability to connect to the database from the host via MySQL Workbench.
  7. Ability to use tools to SFTP/SCP the site into production with Key access.
  8. Ability to modify the NGINX and in some cases my.cnf when needed.
  9. Creation of an easy start/stop maintenance script for the host machine.
  10. Access to logs for MySQL and NGINX for testing

Lesson 2: There is a requirement to be able to pass configuration from the HOST to the DOCKER IMAGE and into the Web Application system.

Integration:

It takes a fair amount of configuration to make passing environment variables through in an automated way to create configurations and resources. There are different considerations when you need data persistence both to a DOCKER volume and to a Host Volume.

A development application is not integrated if you need to be a docker CLI expert to get it started and configured.

Lesson 3: Nginx requires a number of modifications from a default installation to a working development site.

NGINX

/etc/nginix/nginx.conf (needs to support includes)

  1. NGINX does not support environment variables for server configuration until you add perl_set. After much investigation, there is no way to use environment variables for the server_name. The final incarnation uses a sed argument to change the server_name.
  2. NGINX needs a configuration for a default server separate from your app. This helps to debug scripts and if there are hostname issues.
  3. Creating TLS self-signed certificates inside of start scripts requires a little research.
  4. There are different considerations with NGINX and PHP. Including modifying the PHP.ini and having both the CLI and FPM modules installed.
  5. There are two ways to change the debug level of the log files after the image starts. One way is to recreate the file, the other is to embed an include. You can’t use variable substitution on the configuration for the error log levels.

Lesson 4: The base Ubuntu image needs additional packages to make it useful.

Ubuntu 18.04 LTS

For the sake of keeping things updated and secure, I added updating the patches at run-time.

# Create a LEMP docker iamge with NGINX and MYSQL
FROM ubuntu:22.04
USER root
LABEL Description="Development environment for LEMP stack, based on Ubuntu 22.04 LTS. Includes .htaccess support and popular PHP8.1 features, including composer and mail() function."
LABEL License="GNU Public License 3.0"
LABEL Usage="docker run -d -p [HOST WWW PORT NUMBER]:80 -p [HOST WWW TLS PORT NUMBER]:443 -p [HOST DB PORT NUMBER]:3306 -v [HOST WWW DOCUMENT ROOT]:/data -v cbitterfield/lemp"
LABEL Version="2.0"
LABEL maintainer="Colin Bitterfield <colin@bitterfield.com>"
LABEL Author="Colin Bitterfield <colin@bitterfield.com>"


#Used for configuring the base image
ARG WEBSITE=mywebsite.local
ARG WEBSITE_UID=1500
ARG WEBSITE_USER='dev_site'
ARG LEMP_GRP=1000
ARG DATE_TIMEZONE=UTC
ARG DEBIAN_FRONTEND=noninteractive

# Create Data Directories
RUN mkdir /data
RUN mkdir /data/html
RUN mkdir /data/mysql
# Create the logs directories
RUN (mkdir /data/logs; mkdir /data/logs/nginx;mkdir /data/logs/mysql)
# Create the configuration directories
RUN (mkdir /data/conf;mkdir /data/conf/nginx;mkdir /data/conf/nginx/sites-available;mkdir /data/conf/nginx/sites-enabled;mkdir /data/conf/nginx/conf.d; mkdir /data/conf/mysql; mkdir /data/conf/mysql/conf.d;mkdir /data/conf/nginx/tls)

# Create Data Directories for examples
RUN mkdir /.data
RUN mkdir /.data/html
RUN mkdir /.data/mysql
RUN mkdir /.data/conf
# Create the logs directories
RUN (mkdir /.data/logs; mkdir /.data/logs/nginx;mkdir /.data/logs/mysql)
# Create the configuration directories
RUN (mkdir /.data/conf/nginx; mkdir /.data/conf/nginx/sites-available;mkdir /.data/conf/nginx/sites-enabled;mkdir /.data/conf/nginx/conf.d; mkdir /.data/conf/mysql; mkdir /.data/conf/mysql/conf.d;mkdir /.data/conf/nginx/tls)

# Update the operating system when we start and again at the end.
RUN apt-get update
RUN apt-get upgrade -y

# Add Basic Utilities needed
RUN apt-get install -y zip unzip
RUN apt install debconf-utils sudo -y
RUN apt-get install git nodejs npm composer nano tree vim curl ftp ssh certbot -y

# Add Python 3.8
RUN apt install software-properties-common -y
RUN add-apt-repository ppa:deadsnakes/ppa -y
RUN apt install python3.8 -y

#Setup Email
RUN apt-get install postfix -y

# Install PHP and Modules
# Install FPM and FPM SQL Modules
RUN apt-get install php-fpm php-mysql libfcgi0ldbl -y
# Install CLI modules
RUN apt-get install \
php8.1 \
php8.1-bz2 \
php8.1-cgi \
php8.1-cli \
php8.1-common \
php8.1-curl \
php8.1-dev \
php8.1-enchant \
php8.1-fpm \
php8.1-gd \
php8.1-gmp \
php8.1-imap \
php8.1-interbase \
php8.1-intl \
php8.1-json \
php8.1-ldap \
php8.1-mbstring \
php8.1-mysql \
php8.1-odbc \
php8.1-opcache \
php8.1-pgsql \
php8.1-phpdbg \
php8.1-pspell \
php8.1-readline \
php8.1-recode \
php8.1-snmp \
php8.1-sqlite3 \
php8.1-sybase \
php8.1-tidy \
php8.1-xmlrpc \
php8.1-xsl \
php8.1-zip -y

# Configure PHP and set php.ini file location


# Install NGINX and prepare example fines
RUN apt-get install nginx nginx-extras -y
RUN sed -i '/include \/etc\/nginx\/conf.d/c \\tinclude \/data\/conf\/nginx_conf.d\/http_*.conf;' /etc/nginx/nginx.conf
RUN sed -i '/include \/etc\/nginx\/modules-enabled/c include \/data\/conf\/nginx\/conf.d\/pre_http_*.conf;' /etc/nginx/nginx.conf
RUN sed -i '/include \/etc\/nginx\/sites-enabled/c \\tinclude \/data\/conf\/nginx\/sites-enabled\/*.conf;' /etc/nginx/nginx.conf
RUN sed -i '/error_log \/var\/log\/nginx\/error.log;/c \\tinclude \/data\/conf\/nginx\/conf.d\/error_default.conf;' /etc/nginx/nginx.conf


RUN unlink /etc/nginx/sites-enabled/default
COPY default_site.conf /.data/conf/nginx/sites-available/default_site.conf
COPY dev_site.conf /.data/conf/nginx/sites-available/dev_site.conf
COPY dev_site_tls.conf /.data/conf/nginx/sites-available/dev_site_tls.conf
COPY index.php /.data/html/
COPY http_nginx.conf /.data/conf/nginx/conf.d/
COPY pre_http_nginx.conf /.data/conf/nginx/conf.d/

# Add MySQL aka MarianDB
RUN apt-get install mariadb-common mariadb-server mariadb-client -y
RUN echo '!includedir /data/conf/mysql/' >> /etc/mysql/my.cnf
COPY docker_mysql.cnf /etc/mysql/mariadb.conf.d/99-mysqld_datadir.cnf

# Set debian conf
COPY debconf.selections /tmp/
RUN debconf-set-selections /tmp/debconf.selections


# Set Environment Variables And Defaults
#Environment variables for use in running image
ENV LOG_LEVEL="info"
ENV TERM="xterm-256color"
ENV LOG_STDOUT="**notdefined**"
ENV MYSQL_ROOT_PASS="**notdefined**"
ENV MYSQL_DATABASE="**notdefined**"
ENV MYSQL_USER="**notdefined**"
ENV MYSQL_USER_PASS="**notdefined**"
ENV SITE_PASS="**notdefined**"
ENV SSH_PUBLIC="**notdefined**"
ENV TLS="**Boolean**"




# Create a user container for development website
RUN groupadd -g $LEMP_GRP lemp
RUN useradd --comment 'Default Development Website' --no-create-home --home-dir /data/html --uid $WEBSITE_UID --user-group --shell /bin/bash $WEBSITE_USER
RUN usermod -a -G sudo dev_site
RUN usermod -a -G dev_site www-data
RUN usermod -a -G lemp www-data
RUN usermod -a -G lemp mysql
RUN usermod -a -G lemp dev_site
RUN passwd --lock dev_site


#Set Permissions for default structure
RUN chown -R root:lemp /.data
RUN chown -R $WEBSITE_USER:www-data /.data/html
RUN chown -R mysql:mysql /.data/mysql
RUN chmod -R 775 /.data


# Create start script and control scripts for LEMP
COPY lemp-start /usr/sbin/
RUN chmod +x /usr/sbin/lemp-start


#Exernal Volume and Network Connectons Defined
VOLUME /data

EXPOSE 22
EXPOSE 80
EXPOSE 443
EXPOSE 3306

# Update the operating system when we start and again at the end.
# Make sure the image is current when we run for the first time.
RUN apt autoremove -y

#Run Initialization Script
CMD ["/usr/sbin/lemp-start"]

Lesson 5: Using host and DOCKER volumes makes things twice as complicated.

Volumes

Using a DOCKER volume is a snap. One command and you have persistence with your data and an auto-copy from the filesystem. When you want the option of a host volume everything has to be done manually.

Lesson 6: Shell scripting in BASH has changed. It’s not your father’s shell any longer.

BASH

I spent about four hours debugging and modernizing my shell-code. At a minimum, you will need two scripts for a good DOCKER implementation. One for starting the image at run-time and one to use as a friendly service on your host machine.

Creating a good start/stop script that can build based on various permutations of command-line variables is not trivial. See example start/stop.

NAME=LEMP
INSTANCE="cbitterfield/lemp:latest"
#Build the docker command based on parameters:
#It will be docker run CMD
#
CMD="/usr/local/bin/docker run --name $NAME -it "
if [ "$SSH_PORT" ]; then CMD="$CMD -p $SSH_PORT:22" ; fi
if [ "$HTTP_PORT" ]; then CMD="$CMD -p $HTTP_PORT:80" ; fi
if [ "$HTTPS_PORT" ]; then CMD="$CMD -p $HTTPS_PORT:443 " ; fi
if [ "$MYSQL_PORT" ]; then CMD="$CMD -p $MYSQL_PORT:3306 " ; fi
if [ "$WEBSITE" ]; then CMD="$CMD --add-host $WEBSITE:127.0.0.1 " ; fi
CMD="$CMD --env WEBSITE --env LOG_STDOUT --env LOG_LEVEL --env MYSQL_USER --env TLS --env MYSQL_USER_PASS --env MYSQL_ROOT_PASS --env SITE_PASS --env DATABASE"
if [ "$MOUNT_POINT" ]; then CMD="$CMD -v $MOUNT_POINT " ; fi
CMD="$CMD $INSTANCE"
echo "Full Command: $CMD"

I found a good online utility for testing the bash scripts for potential errors and modernization. ShellCheck

for instance, using a multi-command with a cd is problematic.something like (cd /dir; do something; do more) is not good.
it has to be changed to (cd /dir || exit; do something; do more)
It is bad form to declare and export variables on the same line. You have to use shell expansion to make sure everything works.VAR=${VAR:-"Default"} to make sure there is a value and.Setting and working with variables that may or may not be set requires adding a considerable amount of logic.I used this construct in the docker file for all of the variables.
# Set Environment Variables And Defaults
#Environment variables for use in running imageENV LOG_LEVEL="info"
ENV TERM="xterm-256color"
ENV LOG_STDOUT="**notdefined**"
ENV MYSQL_ROOT_PASS="**notdefined**"
ENV MYSQL_DATABASE="**notdefined**"
ENV MYSQL_USER="**notdefined**"
ENV MYSQL_USER_PASS="**notdefined**"
ENV SITE_PASS="**notdefined**"
ENV SSH_PUBLIC="**notdefined**"\
ENV TLS="**Boolean**"
This way all of the variables were allocated prior to run-time.

Lesson 7: Docker documentation is evolving and is not always as clear as it might be.

DOCKER file

ARG is a variable for building
ENV is a variable for when the container starts
You need to configure the TimeZone, user for build (aka root), and tell ubuntu not to ask questions:ARG DATE_TIMEZONE=UTC
USER root
ARG DEBIAN_FRONTEND=noninteractive
The VOL command creates a docker volume at run-time and copies the same directory from the image to the VOLUME. Host volumes that are mounted are put over the VOL directory and make it blank. If you want to use a host VOLUME you have to copy the files to the new volume manually.It is not always apparent when to run a command in the DOCKER file and when to run it in the start script.Make sure to define all of your variables in the DOCKER file for future builds. Creating users, groups and permissions are mostly done in DOCKER file and some must be done in the start script. Any permissions that have to be set on run-time configurations need to happen at runtime.

Lesson 8: When you are creating an image it needs to be tested and documented to increase its value.

Testing:

Use case testing is very important in any application and a docker image is no different.

During the build process, I created aliases in bash for building and clearing out the old images. This help in reducing the typing and made sure I didn’t make a typo based error.

The hardest part of testing this build occurred during the NGINX issues. It gave me a good lesson in remembering having good users, groups, and permissions. I spent at least two days dealing with NGINX permissions and configurations. Eventually, I found some articles that helped to get the variables working and the server configs created.

I also created a default index.php that tests the web server, the PHP integration, PHP integration with MySQL.

Testing and debugging the BASH scripts took about 4 hours. I wanted to make sure that they were written with best practices and did not create additional security issues. I find it best to work from the standards and best practice basics regardless of the project otherwise, I will find the problem in some bad future way.

This site had a good article on testing PHP-FPM.

You need to use CURL and MYSQL clients to test inside of the DOCKER image. and test externally from the host as well.

Lesson 9: What files, directories, users and groups that need to be created or modified along the way.

Files, Directories, Users, and Groups

Files I created:

/etc/nginx/nginx.conf
/etc/mysql/my.cnf
./html/index.php
debconf-set-selections
lemp-start - Docker run start script.
docker_mysql.cnf
lemp_service.sh
pre_http_nginx.conf
nginx_env.conf
http_nginx.confdev_site.conf
/nginx/sites-available/default_site.conf
[ To replace the normal default]
setup_environment.sh

Files I modified:

/nginx/sites-available/default_site.conf 
[ To replace the normal default]
mariadb.conf.d/99-mysqld_datadir.cnf
[To change the default

Directories Created:


./html
./logs
./logs/nginx
./logs/mysql
./conf
./conf/nginx
./conf/nginx/conf.d
./conf/nginx/tls
./conf/nginx/sites-enabled
./conf/nginx/sites-available
./conf/mysql
./conf/mysql/conf.d
./mysql

When creating directories, I found that I need to use a single command with parentheses for each subdirectory due to how docker created intermediate volumes.

RUN (mkdir /.data/logs; mkdir /.data/logs/nginx;mkdir /.data/logs/mysql)

Users:

useradd --comment 'Default Development Website' --no-create-home --home-dir /data/html  --uid $WEBSITE_UID --user-group  --shell /bin/bash $WEBSITE_USER

Groups:

groupadd -g $LEMP_GRP lempand added users to groups:
usermod -a -G sudo dev_site
usermod -a -G dev_site www-data
usermod -a -G lemp www-data
usermod -a -G lemp mysql
usermod -a -G lemp dev_site

Lesson 10: Building Docker Images

Docker Building

During this build, I probably built and re-built the image more than 1000 times. Creating some aliases in conjunction with the service script helps a lot.

Aliases

alias lemp_build='docker build -t cbitterfield/lemp /Users/colin/Github/lemp'alias lemp_service='/Users/colin/Github/lemp/lemp_service.sh'alias lemp_test='docker run -it  -p 8080:80 -p 2222:22 -p8444:443 -p 6306:3306 --name=LEMP4U cbitterfield/lemp:latest /bin/bash'

lemp_service.sh

#!/bin/bash# Author Colin A. Bitterfield
# Source code available on github.com/cbitterfield/lemp
# License: GNU Public 3.0
# Set Default Environment Variables here
export LOG_STDOUT=${LOG_STDOUT:-"/data/logs/nginix/dev_site/access.log"}
export LOG_LEVEL=${LOG_LEVEL:-info}
export WEBSITE=${WEBSITE:-website.local}
export SSH_PORT=${SSH_PORT:-"2222"}
export MYSQL_PORT=${MYSQL_PORT:-"6306"}
export HTTP_PORT=${HTTP_PORT:-"8080"}
export HTTPS_PORT=${HTTPS_PORT:-"8443"}
# Option Values for security; if not defined they are assigned at run time.
export MYSQL_ROOT_PASS=${MYSQL_ROOT_PASS:-**notdefined**}
export MYSQL_DATABASE=${MYSQL_DATABASE:-**notdefined**}
export MYSQL_USER=${MYSQL_USER:-**notdefined**}
export MYSQL_USER_PASS=${MYSQL_USER_PASS:-**notdefined**}
export SITE_PASS=${SITE_PASS:-**notdefined**}
export SSH_PUBLIC=${SSH_PUBLIC:-"**notdefined**"}
export HOST_MOUNT=${HOST_MOUNT:-''}
if [ $HOST_MOUNT ]; then
export MOUNT_POINT="${HOST_MOUNT}:/data"
else
unset MOUNT_POINT
fi
set -e
NAME=LEMP
INSTANCE="cbitterfield/lemp:latest"
#Build the docker command based on parameters:
#It will be docker run CMD
#
CMD="/usr/local/bin/docker run --name $NAME -it "
if [ "$SSH_PORT" ]; then CMD="$CMD -p $SSH_PORT:22" ; fi
if [ "$HTTP_PORT" ]; then CMD="$CMD -p $HTTP_PORT:80" ; fi
if [ "$HTTPS_PORT" ]; then CMD="$CMD -p $HTTPS_PORT:443 " ; fi
if [ "$MYSQL_PORT" ]; then CMD="$CMD -p $MYSQL_PORT:3306 " ; fi
if [ "$WEBSITE" ]; then CMD="$CMD --add-host $WEBSITE:127.0.0.1 " ; fi
CMD="$CMD --env WEBSITE --env LOG_STDOUT --env LOG_LEVEL --env MYSQL_USER --env TLS --env MYSQL_USER_PASS --env MYSQL_ROOT_PASS --env SITE_PASS --env SSH_PUBLIC --env MYSQL_DATABASE"
if [ "$MOUNT_POINT" ]; then CMD="$CMD -v $MOUNT_POINT " ; fi
CMD="$CMD $INSTANCE"
###############################################case "$1" in
start)
echo "Starting docker: $NAME"
echo "website name set to [$WEBSITE]"
if [ "$DATABASE" ]; then echo "database name set to [$DATABASE]" ; fi
if [ "$DATABASE_USER" ]; then echo "database user set to [$DATABASE_USER]" ; fi
if [ "$DATABASE_USER_PASS" ]; then echo "database password set to [$DATABASE_USER_PASS]" ; fi
if [ "$DEVSITE_PASS" ]; then echo "Linux user [dev_site] password is set to [$DEVSITE_PASS]" ; fi
if [ "$SSH_PUBLIC" ]; then echo "Enabling key access to [dev_site] password" ; fi
if [ "$MOUNT_POINT" ]; then echo "MOUNT_POINT is $MOUNT_POINT" ; fi
echo "All undefined values are defaulted on startup and presented to sdtout"
echo "In the user directory is a copy of all of the passswords, delete after you have downloaded it"
# Test to see if a container is already running as LEMP

LEMP="$(docker ps -f name=$NAME -qa)"
if [ "$LEMP" ]; then
echo "Starting an existing container $NAME"
docker start $NAME -a
else
echo "Starting a new container $NAME"
$CMD
fi

;;
stop)
echo -n "Stopping docker: $NAME"
docker container stop $NAME
echo "."
;;

clear)
echo -n "Clearing docker container: $NAME"
docker container stop $NAME
docker container rm $NAME
echo "."
;;

login)
docker exec -it $NAME /bin/bash
;;

status)
echo -n "Getting docker status: $NAME"
docker ps -f name=$NAME
echo "."
;;

show_docker)
printf "Docker Command for usage"
printf "Set the following environment variables"
printf "{ LOG_STDOUT | LOG_LEVEL | WEBSITE | SSH_PORT | MYSQL_PORT | HTTP_PORT | HTTPS_PORT | DEVSITE_PASS | WEBSITE_PASSWORD | MYSQL_ROOT_PASSWORD "
printf "\t DATABASE_USER | DATABASE_USER_PASS }"
printf "docker run -it --name LEMP -p "$HTTP_PORT":80 -p HTTPS_PORT:443 -p "$SSH_PORT":22 \\"
printf "-p "$MYSQL_PORT":3306 --add-host "$WEBSITE":127.0.0.1 \\ "
printf "--env WEBISTE --env LOG_STDOUT --env LOG_LEVEL cbitterfield/lemp:latest "

;;
*)
printf "Usage: %s %s" $1 "[start|stop|status|show_docker|clear]"
exit 1
esac

Lesson 11: Fixing trivial things take forever to research

Trivial Things

Figuring out the best way to use replaceable parameters can take a great deal of research and testing. Many trivial things are the difference between a polished application and a hacked script.

Example 1: Creating the servert.conf for NGINX

Initially, I wanted to use environment variables to configure the LOG_LEVEL and SERVER_NAME in the server.conf file to make it self configuring. I spent a full day on environment variables in NGINX configuration files only to find even if you get it working with perl_set and a module, it doesn’t work on all sections in the config. In the end, I was back to old school “sed” and changing the file after the copy.

Example 2: Showing that php-fpm is started

* Starting Postfix Mail Transport Agent postfix                                                                                                                                               [ OK ]
* Starting OpenBSD Secure Shell server sshd [ OK ]
* php-fpm started [OK]
* Starting nginx nginx [ OK ]
* Starting MariaDB database server mysqld
The server php-fpm does not provide a message when it starts. First I did a complicated ps -ef | grep to find out if it was running. After running my script through shell check, I found there was a better and more correct way.... snip
TCOLS="$(tput cols)"
FCOLS="$(( TCOLS - 16))"
export FCOLS
if service php7.2-fpm start;
then
printf ' * php-fpm started%*s\n' "$FCOLS" "[OK]" > /dev/stdout
fi
Initially I used echo, but then the [OK] message was on the left side of the screen and all of the other [OK] messages were on the right. Another 1/2 day of research yield using printf and getting the screen size from the terminal. During this time, I found out expr is not the best or most correct way.

Example 3: Setting Secure Headers for modern applications.

Secure Headers

Although not really part of my specification, I decided to demonstrate and include secure headers. As an instructor in Cyber Security, Providing a secure coding example is a more correct answer than a trivial example like “It Works…”. I found a good post on the subject and incorporated it into my index.php example.

https://gist.github.com/plentz/6737338

Lesson 12: Manual Builds

Since the initial writing of this, Docker requires a paid subscription for automated builds. These commands will build and push the image up to docker hub. You will need to manually load the titles and ReadMe files for them to populate.

$ docker build -t cbitterfield/lemp:release-2.0 /Users/colin/Github/lemp

$ docker image push cbitterfield/lemp:release-2.0

Lesson 13: Automatic Docker Builds from GitHub

GitHub automated builds

In more discovery, I learned that I can just maintain my Github version and have DOCKER build automatically. Initially, this went nuts because it auto-builds on every change. I quickly started working how to build by tag or branch. I use eclipse and it has an advanced option under git for adding tags by the commit message.

Docker Autobuild

I am sure this will fall under the lesson of trivial things taking longer than expected. Learning this is worth the time.

Autobuild based on tag working

Starting the image for running.

MOUNT="/Users/colin/litecommerce/"
HTTPS_PORT='8443'
HTTP_PORT="8080"
SSH_PORT="2222"
WEBSITE="litecommerce.local"
MYSQL_USER_PASS='Password1\!'
MYSQL_ROOT_PASS='MyPassword1\!'
MYSQL_DATABASE='data_litecommerce'
SITE_PASS='YourPassword1\!'

export MOUNT HTTPS_PORT SSH_PORT WEBSITE MYSQL_USER_PASS MYSQL_ROOT_PASS MYSQL_DATABASE

docker run -it --name LEMP \
-p "$HTTP_PORT":80 -p $HTTPS_PORT:443 -p "$SSH_PORT":22 -p "$MYSQL_PORT":3306 --add-host "$WEBSITE":127.0.0.1 \
--env WEBSITE --env LOG_STDOUT --env LOG_LEVEL --env MYSQL_USER --env TLS --env MYSQL_USER_PASS \
--env MYSQL_ROOT_PASS --env SITE_PASS --env SSH_PUBLIC --env MYSQL_DATABASE \
-v $MOUNT_POINT:/data cbitterfield/lemp:latest

--

--

Colin Bitterfield

NIST certified Security Professional | 10+ years experience in infrastructure security and compliance | Experienced in creating security programs.