Moving from Vagrant to Docker & Docker Compose: Docker compose
Finally making it work with Docker Compose
On the last part , i talked about my try using Kubernetes to orchestrate our local development environment and how it was no good at least at this stage since there was a lot of problems related to mounted volumes, and how pods are communicating so it made more sense to switch to a simpler orchestrator which is Docker Compose.
The compose file
version: "3.2"
services:
mongo:
container_name: mg_mongo
build:
context: ./config/mongo/
restart: always
networks:
default:
aliases:
- mrgeek_mongo
ports:
- "27017:27017"
volumes:
# Mongo config
- ./config/mongo/mongod.conf:/etc/mongod.conf
# Mongo data to be persistent between reboots
- mongo-data:/data/db
rabbitmq:
container_name: mg_rabbitmq
build:
context: ./config/rabbitmq/
restart: always
networks:
default:
aliases:
- mrgeek_rabbitmq
ports:
- "5672:5672"
- "15672:15672"
volumes:
# RabbitMQ configuration
- ./config/rabbitmq/:/etc/rabbitmq/
elasticsearch:
container_name: mg_elasticsearch
build:
context: ./config/elasticsearch/
restart: unless-stopped
networks:
default:
aliases:
- mrgeek_elasticsearch
ports:
- "9200:9200"
volumes:
# Elasticsearch data
- elastic-data:/var/lib/elasticsearch
# Elasticsearch configuration files
- ./config/elasticsearch/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- ./config/elasticsearch/jvm.options:/usr/share/elasticsearch/config/jvm.options
kibana:
container_name: mg_kibana
build:
context: ./config/kibana/
restart: unless-stopped
depends_on:
- elasticsearch
environment:
ELASTICSEARCH_URL: http://mrgeek_elasticsearch:9200
ELASTICSEARCH_HOSTS: http://mrgeek_elasticsearch:9200
ports:
- "5601:5601"
volumes:
# Kibana configuration files
- ./config/kibana/kibana.yml:/usr/share/kibana/config/kibana.yml
- ./config/kibana/node.options:/usr/share/kibana/config/node.options
- ./config/nginx/ssl/:/usr/share/kibana/config/ssl/
main:
container_name: mg_main
build:
# define parent directory as context so we can get things from other
# repositories
context: ./../
dockerfile: ./mg_docker/Dockerfile
depends_on:
- mongo
- rabbitmq
- elasticsearch
ports:
- "443:443"
- "80:80"
- "5222:5222"
- "22:22"
extra_hosts:
# extra hosts
- "mrgeek_api:127.0.0.1"
- "mrgeek_xmpp:127.0.0.1"
- "mrgeek_file_upload:127.0.0.1"
- "mrgeek_reporting:127.0.0.1"
- "host.docker.internal:host-gateway"
volumes:
# Bashrc
- ./config/.bashrc:/root/.bashrc
# dnsmasq.conf
- ./config/dnsmasq/dnsmasq.conf:/etc/dnsmasq.conf
# Nginx configuration from our nginx-edge repo
- ./config/nginx/ssl/:/etc/nginx/ssl/
- ./../mg-nginx/src/conf.d/:/etc/nginx/conf.d/
# Kibana configuration
- ./config/kibana/:/etc/kibana/
# Ejabberd
- ./config/ejabberd/conf/:/opt/ejabberd/conf/
- ejabberd-data:/opt/ejabberd/database/
- ./config/ejabberd/ssl/:/opt/ejabberd/ssl/
# PHP fpm conf
- ./config/api/php-fpm.conf:/etc/php/fpm/php-fpm.conf
- ./config/api/www.conf:/etc/php/fpm/pool.d/www.conf
- ./config/api/opcache.ini:/etc/php/fpm/conf.d/05-opcache.ini
- ./config/api/php.ini:/etc/php/fpm/php.ini
- ./config/api/php.ini:/etc/php/cli/php.ini
#
# Main services
#
# api
- ./../mg_api/:/var/www
- ./../mg_api/composer.lock:/var/www/composer.lock
# mg_xmpp_node
- ./../mg_xmpp/:/var/app/mg_xmpp/
# mg_reporting
- ./../mg_reporting/:/var/app/mg_reporting/
# mg_file_upload
- ./../mg_file_upload/:/var/app/mg_file_upload/
volumes:
mongo-data:
elastic-data:
ejabberd-data:
The first 4 services have common attributes as you can see all of their config we get from a ./config
directory were it has sub-directories for each service it has its own configuration files plus its own Dockerfile
as you can see here:
mg_docker/
├─ config/
│ ├─ api/
│ │ ├─ php-fpm.conf
│ │ ├─ www.conf
│ │ ├─ opcache.ini
│ │ ├─ php.ini
│ ├─ dnsmasq/
│ │ ├─ dnsmasq.conf
│ ├─ elasticsearch/
│ │ ├─ elasticsearch.yml
│ │ ├─ jvm.options
│ │ ├─ Dockerfile
│ ├─ kibana/
│ │ ├─ Dockerfile
│ │ ├─ kibana.yml
│ │ ├─ node.options
│ ├─ ejabberd/
│ │ ├─ conf/
│ │ │ ├─ ejabberd.yml
│ │ │ ├─ ejabberdctl.cfg
│ ├─ rabbitmq/
│ │ ├─ Dockerfile
│ │ ├─ enabled_plugins
│ ├─ mongo/
│ │ ├─ Dockerfile
│ │ ├─ mongod.conf
├─ Dockerfile
├─ docker-compose.yml
├─ docker-entrypoint.sh
mg_api/
mg_xmpp/
mg_reporting/
mg_file_upload/
Important thing to note here is the networks aliases for each service because that's how the services are going to communicate between each other, a good example for this is elasticsearch
container aliases:
networks:
default:
aliases:
- mrgeek_elasticsearch
and we're using this alias in kibana
container passing it to it's configuration as environment variables
environment:
ELASTICSEARCH_URL: http://mrgeek_elasticsearch:9200
ELASTICSEARCH_HOSTS: http://mrgeek_elasticsearch:9200
Now for the main service it has more configuration as you can see, but good thing to notice is that the context is set with context: ./../
and since the context is changed we're referring to the docker file with this dockerfile: ./mg_docker/Dockerfile
Context is changed so we can refer to other services which exist on the parent directory of this one
../
And we're also setting the extra hosts so that services can communicate to each other inside the same container
extra_hosts:
# extra hosts
- "mrgeek_api:127.0.0.1"
- "mrgeek_xmpp:127.0.0.1"
- "mrgeek_file_upload:127.0.0.1"
- "mrgeek_reporting:127.0.0.1"
- "host.docker.internal:host-gateway"
Also we're setting the volumes as well where you can see we're mounting other services
dnsmasq
is used to act as a DHCP server inside the container so we can reference these extra hosts inside the container
The docker files
Docker files for our services are really simple and they actually might be redundant and we can even use docker compose directly for this, but for better and clear control we went with docker files
The one hell of a Dockerfile
that we rely on is the main docker file for our main service which has:
- Nginx
- PHP & PHP-FPM
- Ejabberd
- NodeJs
It is a big one i know, and later on this one will be splitted into multiple services of course, so here is it:
FROM debian:10
###############################################################################
# Install basic software
#
RUN set -x \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
systemd \
sudo \
build-essential \
nano \
openssh-server \
apt-transport-https \
ca-certificates \
gnupg \
curl \
software-properties-common \
locales \
python-pip \
python-setuptools \
python \
unzip
###############################################################################
# Install nginx
#
RUN curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add -
RUN add-apt-repository 'deb https://nginx.org/packages/mainline/debian/ buster nginx'
RUN set -x \
&& apt-get update \
&& apt-get install -y nginx \
&& rm -rf /var/lib/apt/lists/*
###############################################################################
# Install ejabberd
#
ENV EJABBERD_HOME=/opt/ejabberd
ENV EJABBERD_CTL=/opt/ejabberd/bin/ejabberdctl
ENV NGINX_INTERNAL_SERVICE_HOST=localhost
COPY ./mg_ejabberd/ejabberd.deb /root/ejabberd.deb
RUN dpkg -i /root/ejabberd.deb
RUN mkdir -p /var/log/ejabberd
RUN touch /var/log/ejabberd/extauth.log
RUN chown -R ejabberd:ejabberd /var/log/ejabberd
RUN chown -R ejabberd:ejabberd ${EJABBERD_HOME}/
###############################################################################
# Install dnsmasq for nginx resolver
#
RUN apt-get update
RUN apt-get install -y dnsmasq dnsutils net-tools
###############################################################################
# Install php, php-fpm and composer
#
RUN apt-get update
RUN apt-get install -y \
php-cli \
php-fpm \
php-xml \
php-bcmath \
php-mbstring \
php-gd \
php-curl \
php-mongodb \
php-pear \
php-dev \
php-xdebug \
php-apcu
# Setup xdebug
COPY ./mg_docker/config/api/xdebug.ini /etc/php/mods-available/xdebug.ini
# Install mongo extenstion
RUN pecl install mongodb
RUN curl -sS https://getcomposer.org/installer -o /root/composer-setup.php
RUN php /root/composer-setup.php --install-dir=/usr/local/bin --filename=composer
RUN echo "extension=mongodb.so" > /etc/php/cli/conf.d/20-mongodb.ini
RUN echo "extension=mongodb.so" >/etc/php/fpm/conf.d/20-mongodb.ini
###############################################################################
# Some cleanup to reduce space
#
RUN apt-get clean && apt-get autoremove
RUN rm -rf /root/ejabberd.deb
RUN rm -rf /root/composer-setup.php
###############################################################################
# Nginx main configuration
#
COPY ./mg-nginx/src/nginx.conf /etc/nginx/nginx.conf
ARG USER_ID=1000
ARG GROUP_ID=1000
# Remove www-data user
RUN userdel -f www-data &&\
if getent group www-data ; then groupdel www-data; fi
# Generate a new www-data user with IDs 1000
# Will be used by API to create cache & logs
RUN groupadd -g ${GROUP_ID} www-data
RUN useradd -l -u ${USER_ID} -g www-data www-data
RUN usermod -G sudo www-data
RUN install -d -m 0755 -o www-data -g www-data /home/www-data /var/run/php /var/run/nginx
RUN chown --changes --no-dereference --recursive \
${USER_ID}:${GROUP_ID} \
/home/www-data \
/var/lib/php/sessions \
/etc/nginx /var/run/nginx /var/run/php \
/var/cache/nginx \
/var/log/nginx \
/var/run/php \
/var/run/nginx \
/etc/php/
RUN sed -i 's/^\%sudo.*/%sudo ALL=(ALL) NOPASSWD:ALL/g' /etc/sudoers
###############################################################################
# Entrypoint
#
COPY ./mg_docker/docker-entrypoint.sh /tmp/docker-entrypoint.sh
RUN chmod +x /tmp/docker-entrypoint.sh
USER www-data
###############################################################################
# Install nvm and node v14.15.4 and npm v6.14.10
#
ENV NODE_VERSION=14.15.4
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
ENV NVM_DIR=/home/www-data/.nvm
RUN . "$NVM_DIR/nvm.sh" && nvm install ${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm use v${NODE_VERSION}
RUN . "$NVM_DIR/nvm.sh" && nvm alias default v${NODE_VERSION}
ENV PATH="/home/www-data/.nvm/versions/node/v${NODE_VERSION}/bin/:${PATH}"
RUN node --version
RUN npm --version
###############################################################################
# Install pm2
#
RUN npm install -g pm2
ENTRYPOINT ["./tmp/docker-entrypoint.sh"]
###############################################################################
# Expose needed ports
#
EXPOSE 433 80 5222 22
STOPSIGNAL SIGQUIT
CMD ["nginx"]
It's pretty straight forward docker file the only thing to note is this part
ARG USER_ID=1000
ARG GROUP_ID=1000
# Remove www-data user
RUN userdel -f www-data &&\
if getent group www-data ; then groupdel www-data; fi
# Generate a new www-data user with IDs 1000
# Will be used by API to create cache & logs
RUN groupadd -g ${GROUP_ID} www-data
RUN useradd -l -u ${USER_ID} -g www-data www-data
RUN usermod -G sudo www-data
RUN install -d -m 0755 -o www-data -g www-data /home/www-data /var/run/php /var/run/nginx
RUN chown --changes --no-dereference --recursive \
${USER_ID}:${GROUP_ID} \
/home/www-data \
/var/lib/php/sessions \
/etc/nginx /var/run/nginx /var/run/php \
/var/cache/nginx \
/var/log/nginx \
/var/run/php \
/var/run/nginx \
/etc/php/
RUN sed -i 's/^\%sudo.*/%sudo ALL=(ALL) NOPASSWD:ALL/g' /etc/sudoers
Here we're creating new www-data
user with the same UID and GID as the host user which are both 1000
then we're changing the owner of most of services' directories to the new user, so whenever for example PHP is generating logs, which are mounted on the machine, host user can still control them without using sudo
This is also used to run our docker entrypoint as you can see
COPY ./mg_docker/docker-entrypoint.sh /tmp/docker-entrypoint.sh
RUN chmod +x /tmp/docker-entrypoint.sh
ENTRYPOINT ["./tmp/docker-entrypoint.sh"]
`
In which we're running all the services of this container.
This way it was easier to mount files, and even the mounted volumes will have the same exact user as on the host machine. So we will never have to use sudo
to remove a directory for example or change a file.
Final thoughts
It was quite a journey, which is still ongoing BTW but to reach this point where at least the software is up & running and is development friendly is quite amazing
The directory structure we have plays an important role to have this working, where each service is a repository on its own, and exists on the parent directory of this docker project, that's why it was easier to reuse them in docker containers
DevOps indeed helped and fixed some hard issues, and i learned a lot by just touching this bare topic, i was exposed to so many technologies so deep more than i would ever dreamed.