03/02/2018 - DOCKER
In this example we are going to create three containers (Apache, MySQL, PHP-FPM) for a web application.
The most important point here is that, when we remove containers we won't lose any data such as application files, logs and database because they are not stored in containers.
The "logs" and "database" folders get created when we run ./build.sh
file first time so no need to create them manually. You need to run chmod +x docker/build.sh
only once.
ubuntu@linux:~/helloworld$ tree -a
.
├── bad.php
├── docker
│ ├── apache
│ │ ├── Dockerfile
│ │ ├── httpd.conf
│ │ └── httpd-vhosts.conf
│ ├── build.sh
│ ├── destroy.sh
│ ├── docker-compose.yml
│ ├── .env
│ ├── mysql
│ │ └── Dockerfile
│ └── php
│ ├── Dockerfile
│ └── www.conf
└── index.php
4 directories, 12 files
ubuntu@linux:~/helloworld$ cat bad.php
<?php
echo 'This is bad file which will create entry in log file'
ubuntu@linux:~/helloworld$ cat index.php
<?php
echo 'Hello World!'.PHP_EOL;
$servername = getenv('MYSQL_IP');
$username = getenv('MYSQL_ROOT_USER');
$password = getenv('MYSQL_ROOT_PASSWORD');
$conn = mysqli_connect($servername, $username, $password);
if (!$conn) {
exit('Connection failed: '.mysqli_connect_error().PHP_EOL);
}
echo 'Successful database connection!'.PHP_EOL;
ubuntu@linux:~/helloworld$ cat docker/build.sh
#!/bin/bash
set -e
if ! [[ -d ../logs/apache ]]; then
mkdir -p ../logs/apache
fi
if ! [[ -d ../logs/mysql ]]; then
mkdir -p ../logs/mysql
fi
if ! [[ -d ../logs/php ]]; then
mkdir -p ../logs/php
fi
if ! [[ -d ../database ]]; then
mkdir ../database
fi
docker-compose up -d --build
docker exec helloworld_apache_con chown -R root:www-data /usr/local/apache2/logs
docker exec helloworld_php_con chown -R root:www-data /usr/local/etc/logs
ubuntu@linux:~/helloworld$ cat docker/destroy.sh
#!/bin/bash
set -e
docker-compose down --volumes
docker rmi helloworld_apache_img helloworld_php_img
ubuntu@linux:~/helloworld$ cat docker/.env
COMPOSE_PROJECT_NAME=helloworld
WEB_USER=www-data
WEB_GROUP=www-data
APACHE_IP=192.168.0.11
APACHE_EXPOSED_PORT=9000
APACHE_ROOT_DIR=/usr/local/apache2
MYSQL_IP=192.168.0.22
MYSQL_CONTAINER_USER=mysql
MYSQL_CONTAINER_GROUP=mysql
MYSQL_ROOT_USER=root
MYSQL_ROOT_PASSWORD=root
MYSQL_DATA_DIR=/var/lib/mysql
MYSQL_LOG_DIR=/var/log/mysql
PHP_IP=192.168.0.33
PHP_APP_DIR=/srv/app
PHP_ROOT_DIR=/usr/local/etc
NETWORK_SUBNET=192.168.0.0/24
ubuntu@linux:~/helloworld$ cat docker/docker-compose.yml
version: '3'
services:
apache_img:
container_name: ${COMPOSE_PROJECT_NAME}_apache_con
build:
context: ./apache
args:
- WEB_USER=${WEB_USER}
- WEB_GROUP=${WEB_GROUP}
- APACHE_ROOT_DIR=${APACHE_ROOT_DIR}
volumes:
- ../logs/apache:${APACHE_ROOT_DIR}/logs
ports:
- ${APACHE_EXPOSED_PORT}:80
networks:
public_net:
ipv4_address: ${APACHE_IP}
environment:
- APACHE_EXPOSED_PORT=${APACHE_EXPOSED_PORT}
- APACHE_ROOT_DIR=${APACHE_ROOT_DIR}
- PHP_IP=${PHP_IP}
- PHP_APP_DIR=${PHP_APP_DIR}
- WEB_USER=${WEB_USER}
- WEB_GROUP=${WEB_GROUP}
mysql_img:
container_name: ${COMPOSE_PROJECT_NAME}_mysql_con
build:
context: ./mysql
args:
- MYSQL_CONTAINER_USER=${MYSQL_CONTAINER_USER}
- MYSQL_CONTAINER_GROUP=${MYSQL_CONTAINER_GROUP}
volumes:
- ../logs/mysql:${MYSQL_LOG_DIR}
- ../database:${MYSQL_DATA_DIR}
networks:
public_net:
ipv4_address: ${MYSQL_IP}
environment:
- MYSQL_CONTAINER_USER=${MYSQL_CONTAINER_USER}
- MYSQL_CONTAINER_GROUP=${MYSQL_CONTAINER_GROUP}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
php_img:
container_name: ${COMPOSE_PROJECT_NAME}_php_con
build:
context: ./php
args:
- WEB_USER=${WEB_USER}
- WEB_GROUP=${WEB_GROUP}
- PHP_ROOT_DIR=${PHP_ROOT_DIR}
working_dir: ${PHP_APP_DIR}
volumes:
- ..:${PHP_APP_DIR}
- ../logs/php:${PHP_ROOT_DIR}/logs
depends_on:
- apache_img
- mysql_img
networks:
public_net:
ipv4_address: ${PHP_IP}
environment:
- PHP_ROOT_DIR=${PHP_ROOT_DIR}
- APACHE_IP=${APACHE_IP}
- APACHE_EXPOSED_PORT=${APACHE_EXPOSED_PORT}
- WEB_USER=${WEB_USER}
- WEB_GROUP=${WEB_GROUP}
- MYSQL_IP=${MYSQL_IP}
- MYSQL_ROOT_USER=${MYSQL_ROOT_USER}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
networks:
public_net:
driver: bridge
ipam:
driver: default
config:
- subnet: ${NETWORK_SUBNET}
ubuntu@linux:~/helloworld$ cat docker/apache/Dockerfile
FROM httpd:2.4
ARG WEB_USER
ARG WEB_GROUP
ARG APACHE_ROOT_DIR
COPY httpd-vhosts.conf ${APACHE_ROOT_DIR}/conf/extra/httpd-vhosts.conf
COPY httpd.conf ${APACHE_ROOT_DIR}/conf/httpd.conf
RUN chgrp -R ${WEB_GROUP} ${APACHE_ROOT_DIR}/conf/httpd.conf \
&& chgrp -R ${WEB_GROUP} ${APACHE_ROOT_DIR}/conf/extra/httpd-vhosts.conf
RUN usermod -u 1000 ${WEB_USER} \
&& groupmod -g 1000 ${WEB_GROUP} \
&& chgrp -R ${WEB_GROUP} ${APACHE_ROOT_DIR}
ubuntu@linux:~/helloworld$ cat docker/apache/httpd.conf
ServerRoot ${APACHE_ROOT_DIR}
Listen 80
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
<IfModule !mpm_prefork_module>
</IfModule>
<IfModule mpm_prefork_module>
</IfModule>
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
<IfModule unixd_module>
User daemon
Group daemon
</IfModule>
ServerAdmin you@example.com
<Directory />
AllowOverride none
Require all denied
</Directory>
DocumentRoot ${APACHE_ROOT_DIR}/htdocs
<Directory ${APACHE_ROOT_DIR}/htdocs>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
<IfModule dir_module>
DirectoryIndex index.php index.html
</IfModule>
<Files ".ht*">
Require all denied
</Files>
ErrorLog /proc/self/fd/2
LogLevel info
<IfModule log_config_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
<IfModule logio_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>
CustomLog /proc/self/fd/1 common
</IfModule>
<IfModule alias_module>
ScriptAlias /cgi-bin/ ${APACHE_ROOT_DIR}/cgi-bin/
</IfModule>
<IfModule cgid_module>
</IfModule>
<Directory ${APACHE_ROOT_DIR}/cgi-bin>
AllowOverride None
Options None
Require all granted
</Directory>
<IfModule headers_module>
RequestHeader unset Proxy early
</IfModule>
<IfModule mime_module>
TypesConfig conf/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
</IfModule>
Include conf/extra/httpd-vhosts.conf
<IfModule proxy_html_module>
Include conf/extra/proxy-html.conf
</IfModule>
<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>
ServerName localhost
ubuntu@linux:~/helloworld$ cat docker/apache/httpd-vhosts.conf
<VirtualHost *:80>
ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://${PHP_IP}:${APACHE_EXPOSED_PORT}${PHP_APP_DIR}/$1
DocumentRoot ${APACHE_ROOT_DIR}/htdocs
<Directory ${APACHE_ROOT_DIR}/htdocs>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_ROOT_DIR}/logs/error.log
CustomLog ${APACHE_ROOT_DIR}/logs/access.log common
</VirtualHost>
ubuntu@linux:~/helloworld$ cat docker/mysql/Dockerfile
FROM mysql:5.7
ARG MYSQL_CONTAINER_USER
ARG MYSQL_CONTAINER_GROUP
RUN sed -i "s/#log-error/log-error/g" /etc/mysql/mysql.conf.d/mysqld.cnf
RUN usermod -u 1000 ${MYSQL_CONTAINER_USER} \
&& groupmod -g 1000 ${MYSQL_CONTAINER_GROUP}
ubuntu@linux:~/helloworld$ cat docker/php/Dockerfile
FROM php:7.1-fpm
ARG WEB_USER
ARG WEB_GROUP
ARG PHP_ROOT_DIR
COPY www.conf ${PHP_ROOT_DIR}/php-fpm.d/www.conf
RUN docker-php-ext-install mysqli
RUN usermod -u 1000 ${WEB_USER} \
&& groupmod -g 1000 ${WEB_GROUP} \
&& chgrp -R staff ${PHP_ROOT_DIR}/php-fpm.d/www.conf
ubuntu@linux:~/helloworld$ cat docker/php/www.conf
[www]
user = ${WEB_USER}
group = ${WEB_GROUP}
listen = 80
listen.allowed_clients = ${APACHE_IP}
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
catch_workers_output = yes
php_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = ${PHP_ROOT_DIR}/logs/error.log
Validate "docker-compose.yml" file and see the mapping.
ubuntu@linux:~/helloworld/docker$ docker-compose config
networks:
public_net:
driver: bridge
ipam:
config:
- subnet: 192.168.0.0/24
driver: default
services:
apache_img:
build:
args:
APACHE_ROOT_DIR: /usr/local/apache2
WEB_GROUP: www-data
WEB_USER: www-data
context: /home/ubuntu/helloworld/docker/apache
container_name: helloworld_apache_con
environment:
APACHE_EXPOSED_PORT: '9000'
APACHE_ROOT_DIR: /usr/local/apache2
PHP_APP_DIR: /srv/app
PHP_IP: 192.168.0.33
WEB_GROUP: www-data
WEB_USER: www-data
networks:
public_net:
ipv4_address: 192.168.0.11
ports:
- 9000:80/tcp
volumes:
- /home/ubuntu/helloworld/logs/apache:/usr/local/apache2/logs:rw
mysql_img:
build:
args:
MYSQL_CONTAINER_GROUP: mysql
MYSQL_CONTAINER_USER: mysql
context: /home/ubuntu/helloworld/docker/mysql
container_name: helloworld_mysql_con
environment:
MYSQL_CONTAINER_GROUP: mysql
MYSQL_CONTAINER_USER: mysql
MYSQL_ROOT_PASSWORD: root
networks:
public_net:
ipv4_address: 192.168.0.22
volumes:
- /home/ubuntu/helloworld/logs/mysql:/var/log/mysql:rw
- /home/ubuntu/helloworld/database:/var/lib/mysql:rw
php_img:
build:
args:
PHP_ROOT_DIR: /usr/local/etc
WEB_GROUP: www-data
WEB_USER: www-data
context: /home/ubuntu/helloworld/docker/php
container_name: helloworld_php_con
depends_on:
- apache_img
- mysql_img
environment:
APACHE_EXPOSED_PORT: '9000'
APACHE_IP: 192.168.0.11
MYSQL_IP: 192.168.0.22
MYSQL_ROOT_PASSWORD: root
MYSQL_ROOT_USER: root
PHP_ROOT_DIR: /usr/local/etc
WEB_GROUP: www-data
WEB_USER: www-data
networks:
public_net:
ipv4_address: 192.168.0.33
volumes:
- /home/ubuntu/helloworld:/srv/app:rw
- /home/ubuntu/helloworld/logs/php:/usr/local/etc/logs:rw
working_dir: /srv/app
version: '3.0'
ubuntu@linux:~/helloworld/docker$ ./build.sh
Creating network "helloworld_public_net" with driver "bridge"
Building apache_img
Successfully built 756224de2345
Successfully tagged helloworld_apache_img:latest
Building mysql_img
Successfully built cbb571547e11
Successfully tagged helloworld_mysql_img:latest
Building php_img
Successfully built 6214b34aec76
Creating helloworld_apache_con ... done
Creating helloworld_php_con ... done
Creating helloworld_mysql_con ...
Creating helloworld_php_con ...
If you want to see the details of each element, you can run docker inspect
command.
ubuntu@linux:~/helloworld/docker$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld_php_img latest 6214b34aec76 2 minutes ago 383MB
helloworld_mysql_img latest cbb571547e11 3 minutes ago 409MB
helloworld_apache_img latest 756224de2345 3 minutes ago 185MB
...
ubuntu@linux:~/helloworld/docker$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a7bbc7d71e55 helloworld_public_net bridge local
...
ubuntu@linux:~/helloworld/docker$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e8adf3e917fb helloworld_php_img "docker-php-entryp..." 4 minutes ago Up 4 minutes 9000/tcp helloworld_php_con
30d7405b37dc helloworld_mysql_img "docker-entrypoint..." 4 minutes ago Up 4 minutes 3306/tcp helloworld_mysql_con
40d387973713 helloworld_apache_img "httpd-foreground" 4 minutes ago Up 4 minutes 0.0.0.0:9000->80/tcp helloworld_apache_con
As you can see below, "logs" and "database" folders have been created automatically and contain some initial files in them. When we start using our application, there will be more files in each and logs will be updated as well. There isn't any log file in "php" folder yet because we have't started using our application yet.
ubuntu@linux:~/helloworld$ tree -a
.
├── database
│ ├── ...
│ ├── mysql
│ │ ├── ...
│ │ └── ...
│ ├── performance_schema
│ │ ├── ...
│ │ └── ...
│ └── sys
│ ├── ...
│ └── ...
└── logs
├── apache
│ ├── access.log
│ ├── error.log
│ └── httpd.pid
├── mysql
│ └── error.log
└── php
12 directories, 299 files
ubuntu@linux:~/helloworld$ curl 192.168.0.11
Hello World!
Successful database connection!
ubuntu@linux:~/helloworld$ curl localhost:9000
Hello World!
Successful database connection!
Access logs updated.
ubuntu@linux:~/helloworld$ cat logs/apache/access.log
192.168.0.1 - - [03/Feb/2018:16:13:16 +0000] "GET / HTTP/1.1" 200 45
192.168.0.1 - - [03/Feb/2018:16:13:37 +0000] "GET / HTTP/1.1" 200 45
When you run this example, it will silently break but create a log file because "bad.php" file is broken.
ubuntu@linux:~/helloworld$ curl 192.168.0.11/bad.php
ubuntu@linux:~/helloworld$ cat logs/php/error.log
[03-Feb-2018 16:24:06 UTC] PHP Parse error: syntax error, unexpected end of file, expecting ',' or ';' in /srv/app/bad.php on line 4
It's up to you how to manage the permissions of files and folder in/out of containers. I added two lines at the bottom of "build.sh" file but if you want, you can remove them to see what changes it makes in container and host OS.