Introduction
Image of the srvmailrelay container
DockerFile
DockerFile configuration file
DockerFile executable file (bin)
DockerFile executable file (bin) DFG
DockerFile executable (bin) run
Creating the image from the Dockerfile
Using the container from the image
Manual container initialization
Initializing the container with Docker-Compose
Introduction
In the light of your interest in docking, we will see the part of the dockerization of the relay server, the big advantage of dockeriser this service is that it changes very little. I created an image specifically for the training to reduce the number of parameters, to simplify the demonstration. For the little context, this image is used on all my “datacenters”, good good I’m excited a little there, I should say on my server at the data center and at home: P. The only thing left is that I got pissed off once and now I can use it everywhere by setting parameters according to location: D. In other words I will pass in the name of the relay server, by the very fact that my ISP is BELL, Videotron, Roger can be important when starting, I will indicate the destination. This will be the same principle for networks that I authorize to use the SMTP service of my server.
Let’s see the configuration now that we are comfortable with postfix, it will be easier.
The definition of the image is available HERE: SrcMailRelay container
Image of the srvmailrelay container
It is part, for the pleasure of young and old: D.
Let’s start with the DockerFile and then we will see the files used.
DockerFile
Here is the content:
FROM debian:jessie
MAINTAINER Uri Savelchev <alterrebe@gmail.com>
# Ignore APT warnings about not having a TTY
ENV DEBIAN_FRONTEND noninteractive
# Packages: update
RUN apt-get update -qq &&
apt-get install -qq -y --no-install-recommends postfix ca-certificates
libsasl2-modules python-pip supervisor rsyslog &&
pip install j2cli
# Fix timezone
RUN ln -s -f /usr/share/zoneinfo/Canada/Eastern /etc/localtime
# Add files
ADD conf /root/conf
# Configure: supervisor
ADD bin/dfg.sh /usr/local/bin/
ADD conf/supervisor-all.conf /etc/supervisor/conf.d/
# Runner
ADD run.sh /root/run.sh
RUN chmod +x /root/run.sh
# Declare
EXPOSE 25
CMD ["/root/run.sh"]
Well now that I look at it, there are points that bother me, this container dates and there are things I like less … But hey. As you can see I left the name of the original maintainer. I was part of an image available on the internet and I made some changes.
FROM debian: jessie: So I started from a debian version jessie, [Debian 8.7 was released January 14th, 2017) (https://www.debian.org/releases/stable/)
ENV DEBIAN_FRONTEND noninteractive: A definition of an environment variable so that it does not pose a question, which could not be answered because it must be automatic.
RUN apt-get update, apt-get install and pip install: We realize the installation of the required pacques for the system as you can see there is of course postfix as well as the system of log rsyslog. In addition there is other pasting python-pip and supervisor, I install the python-pip system because in order to modify the postfix configuration file I will use the jinja2 system which is a template system. This will allow me to change the configuration of postfix by passing parameters during startup. The application I will use is j2cli documentation. For supervisor this application allows to start applications in foreground (Forground) this will keep the container running while using the command service, even if the application is by default put in the background.
RUN ln -s -f / usr / share / zoneinfo / Canada / Eastern / etc / localtime: Good here it’s less beautiful, I define the timezone of the container directly in the image instead of using the environment variable TZ = America / Montreal. This is less good because if I had a “datacenter” in Morocco for example I should create another image for the server is the right time. While if I had passed the variable at startup this could have been configuration during initialization. (I was lazy, to change it: P, we’ll fight for others: D)
ADD conf / root / conf: Copy of the configuration files, we will see this is the postfix “templates” files
- ADD bin / dfg.sh / usr / local / bin /: Addition of a script realized by Uri, well it works, I find that it broke a little head, but learn methods of all to create our configuration: D. We will take a look at the script too.
ADD conf / supervisor-all.conf /etc/supervisor/conf.d/: Foreground application startup system configuration file (forground)
ADD run.sh /root/run.sh and RUN chmod + x /root/run.sh: Container start script
EXPOSE 25: Indicates that port 25 must be exposed, I personally set this configuration with when initializing the container.
CMD [“/root/run.sh”]: Entry point when starting the container.
Nothing fabulous the whole treatment is really in the startup script and the modification of the configuration files.
Let’s start with the configuration files.
DockerFile fichier de configuration
As mentioned all the configuration files are in the conf directory, there are 2 files:
postfix-main.cf: This file contains the description of the configuration of postfix, this file is of type jinja2, let’s look at the content:
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = $myorigin
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = {{ RELAY_HOST_NAME }}
mydestination = {{ RELAY_HOST_NAME }}
{% if (EXT_RELAY_HOST != "Not_DEFINE") %}relayhost = {{ EXT_RELAY_HOST }} {% endif %}
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 {{ ACCEPTED_NETWORKS }}
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4
We find all postfix configurations that we have already covered, I will not go back on the subject, I will focus on the variables in the file that are framed with {{and}}
myorigin = {{RELAYHOSTNAME}}: This variable will be the name of the machine that will be presented when sending (hostname)
mydestination = {{RELAYHOSTNAME}}: By default in the configuration I define that the machine accepts emails for its machine name.
{% if (EXTRELAYHOST! = “NotDEFINE”)%} relayhost = {{EXTRELAYHOST}} {% endif%}: A little more complicated, to put in context I wanted to have the same image at the data center (OVH) and at home, unfortunately at home I am obliged to go through my ISP. As part of OVH emailing can be done directly, so I put a condition. If the variable {{EXTRELAY_HOST}} is not equal to “Not_DEFINE” then the value is assigned to the relayhost statement. It’s a simple if: D.
mynetworks = … {{ACCEPTED_NETWORKS}}: Allows you to further define by setting the networks that are allowed to transmit their emails by this machine.
Of course the number of parameters is not limited, we will see the substitution process.
supervisor-all.conf: The configuration for the supervisor system, in this case the configuration is “static”:
[supervisord]
logfile = /var/log/supervisord.log
logfile_backups = 0
[program:rsyslogd]
command = /usr/sbin/rsyslogd -n
[program:postfix]
command = /usr/local/bin/dfg.sh /var/spool/postfix/pid/master.pid /etc/init.d/postfix start
startsecs = 3
DockerFile executable file (bin)
DockerFile executable file (bin) DFG
If we look a little higher you will see that the postfix startup command uses a homemade script named dfg for the Forground daemon. As mentioned before this is to keep a process in the foreground (forground) even if the process was put in the background by the system as is the case when using scripts under / etc / init.d
As mentioned I personally would not have done as such, but just as much shown other way to do after all it is also used to that the free to learn from others! It can always give ideas for another project / need!
#! /usr/bin/env bash
set -eu
# dfg: Daemon Foreground
# Runs in foreground while a daemon is running and proxies signals to it.
# As a result, a daemonizing process can be run with supervisor
function display_help(){
cat <<EOF
dfg: Daemon Foreground
Starts a daemon and runs in foreground while the daemon is active, and proxies signals.
As a result, a daemonizing process can be run with supervisor.
Usage: $(basename $0)
EOF
}
[ $# -lt 2 ] && display_help
# Arguments
pidfile="$1"
shift
command=$@
# Go foreground, proxy signals
function kill_app(){
kill $(cat $pidfile)
exit 0
}
trap "kill_app" SIGINT SIGTERM
# Launch daemon
$command
sleep 2
# Loop while the pidfile and the process exist
while [ -f $pidfile ] && kill -0 $(cat $pidfile) ; do
sleep 0.5
done
exit 1000
Good soon you can have fun, take it, modify it, do some tests, some point:
[$ # -lt 2] && display_help: If there are no 2 parameters to the script the system displays the help with the display_help function.
pidfile = “$ 1”: The first parameter is the file containing the process number (PID)
shift: the system remove the first parameter from the command line
command = $ @: assign to the variable $ command the entire remaining command line (as done after the shift all but the pid file)
$ command: Start of the command passed in parameter.
while: The loop system
[-f $ pidfile]: It validates that the pid file is still present
kill -0 $ (cat $ pidfile): In addition to validating the presence of the file, it validates that the PID is working by sending the signal 0, so nothing of all at the number of the process.
If the file AND sends the sign works then it performs a sleep 0.5 seconds.
So that’s basically the important point: D, now it’s up to you to have fun !!
DockerFile executable (bin) run
Place the container execution file, the most important! We will see variable substitution and postfix startup.
Let’s see the contents of the file:
#! /usr/bin/env bash
set -e # exit on error
# Variables
export EXT_RELAY_HOST=${EXT_RELAY_HOST:-"email-smtp.us-east-1.amazonaws.com"}
export EXT_RELAY_PORT=${EXT_RELAY_PORT:-"25"}
export RELAY_HOST_NAME=${RELAY_HOST_NAME:-"relay.example.com"}
export ACCEPTED_NETWORKS=${ACCEPTED_NETWORKS:-"192.168.0.0/16 172.16.0.0/12 10.0.0.0/8"}
export OTHER_MY_DEST
echo $RELAY_HOST_NAME > /etc/mailname
# Templates
j2 /root/conf/postfix-main.cf > /etc/postfix/main.cf
# Launch
rm -f /var/spool/postfix/pid/*.pid
exec /usr/bin/supervisord -n
Yep is not long: D, then we like it, it’s easier to understand!
Definition of the variables, I will take one for the example, but the concept applies for all.
export RELAY_HOST_NAME=${RELAY_HOST_NAME:-"relay.example.com"}
We assign the environment variable RELAY_HOST_NAME to RELAY_HOST_NAME, uh, it’s the same name … (I know, we’re in the morning, I have not got a bus yet !!) all this plays afterwards: – “rela .. If the variable RELAY_HOST_NAME is empty no value then it will assign the default value which is defined after the characters:: -. Magic !! We learn every day a little more to use bash: D. set of variables this does not oblige us all to define: D.
Assigning the machine name to the file / etc / mailname, some application uses it is cleaner.
j2: IMPORTANT all the manipulation of the configuration file is realized here, with the command j2 (installed with pip) the system will take the environment variables and make the substitution. By default the result is displayed on the screen so we redirect to the correct postfix file: /etc/postfix/main.cf
rm * .pid: A little cleanup of the pid files in case there are some data that drag: D.
Start supervisord for the service.
And here we have our image ready to use!
Creating the image from the Dockerfile
Petit recalls the use of docker build to create the final image that you can move wherever you want. We have already seen the order, but for those who have not followed the other courses or all simply a reminder because you have not used much.
Realization of the creation of the image, go to the directory where it finds the Dockerfile:
$ docker build -t srvmailrelay .
-t srvmailrelay: this is the name of the image that I have assigned of course free to you to change it.
Let’s validate it all:
$ docker images | grep srvmailrelay
srvmailrelay latest d42998a69f7c 23 hours ago 182 MB
Yeah we can go to the other section: D.
Using the container from the image
I hope the explanation was not too fast, you’ll have time to digest all of it quietly, let’s move on to using the container now. How to initialize it properly, we’ll see the manual operation and the prettier operation with the docker-compose.
Manual container initialization
When I say manual, I mean with the docker run statement and all the parameters on the command line, this is very handy when testing but when setting “in production” this to these limits.
As a reminder we had some variables available:
RELAY_HOST_NAME=relay.example.com
: Machine name (hostname) that will be assigned to the SMTP serverACCEPTED_NETWORKS=192.168.0.0/16
: The list of networks that are allowed to transmit their email by the server
172.16.0.0/12 10.0.0.0/8EXT_RELAY_HOST=email-smtp.us-east-1.amazonaws.com
: The name of the relay server if used.
We could have had more option, but it’s already good: D, free to you to add what you need :).
So if we initialize the container, here is an example of use:
$ docker run -d -h relay.example.com -e ACCEPTED_NETWORKS='192.168.0.0/16 172.16.0.0/12 10.0.0.0/8' -e EXT_RELAY_HOST=relay.mon_fai.com srvmailrelay
4670b8a8ea8a86381ed77d08f1cf603128b0adbf3c7f720dad4d7e86820e5e24
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4670b8a8ea8a srvmailrelay "/root/run.sh" 45 seconds ago Up 44 seconds 25/tcp distracted_lamport
We will recover a shell on the container to validate the configuration.
$ docker exec -it mailrelay bash
root@relay:/# cat /etc/postfix/main.cf
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = $myorigin
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = relay.example.com
mydestination = relay.example.com
relayhost = relay.mon_fai.com
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4
root@relay:/#
You can use the template jinja2 and validate that all the variables were substituted, quickly:
relayhost = relay.monfai.com : uses the variable EXT_RELAY_HOST=relay.monfai.com
mynetworks = …192.168.0.0/16 172.16.0.0/12 10.0.0.0/8 : uses the variable ACCEPTED_NETWORKS=’192.168.0.0/16 172.16.0.0/12 10.0.0.0/8′
I would like to focus on the myorigin parameter: if we look at the template file it should be the value of the RELAY_HOST_NAME variable. A value was assigned when the container was initialized when calling the run.sh script.
We validate the other configuration WITHOUT relayhost: D.
$ docker run -d -h relay.example.com -e ACCEPTED_NETWORKS='192.168.0.0/16 172.16.0.0/12 10.0.0.0/8' -e EXT_RELAY_HOST=Not_DEFINE srvmailrelay 614f2846a28e5b1adbeac48a199d0e3f117e42822137493d7fbb425b0df73136
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
614f2846a28e srvmailrelay "/root/run.sh" 13 seconds ago Up 11 seconds 25/tcp ecstatic_bose
4670b8a8ea8a srvmailrelay "/root/run.sh" 3 minutes ago Up 3 minutes 25/tcp distracted_lamport
$ docker exec -it ecstatic_bose bash
root@relay:/# cat /etc/postfix/main.cf
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = $myorigin
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = relay.example.com
mydestination = relay.example.com
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4
Yeah it works, as you can see now the relayhost entry is no longer present: D, because in the template if the variable EXT_RELAY_HOST == Not_DEFINE it allows the system to transmit directly to the internet. I know my condition is not great, at least Not_DEFINE should be something like DIRECT_TO_THE_WORLD, it will do you an exercise: P.
As you can see I have 2 containers that are running at the same time very convenient for testing !!
Well that’s good, it works but in 3 months if you need to restart it, you will look in your bash history and it is likely that the order neither is more. So there “Panic Office”: P, that’s why I strongly urge you to use a docker-type file
Ok I will delete the current containers:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
614f2846a28e srvmailrelay "/root/run.sh" 8 minutes ago Up 8 minutes 25/tcp ecstatic_bose
4670b8a8ea8a srvmailrelay "/root/run.sh" 11 minutes ago Up 11 minutes 25/tcp distracted_lamport
$ docker stop ecstatic_bose distracted_lamport
ecstatic_bose
distracted_lamport
$ docker rm ecstatic_bose distracted_lamport
ecstatic_bose
distracted_lamport
Initializing the container with Docker-Compose
You can use a configuration file that will allow the initialization of your container, the advantage of course is to have a file on the system with all the initialization information. This also allows you to add a lot of options simply and to consult them easily.
I put an example of the file in the directory of the srvmailrelay container definition.
Here is the content of the docker-compose.yml file:
mailrelay:
image: 'srvmailrelay'
restart: unless-stopped
container_name : 'mailrelay'
hostname: 'relais.example.com'
environment:
- ENABLE_SASL_AUTH=no
- RELAY_HOST_NAME=monServerDeRelaisInterne.example.com
- ACCEPTED_NETWORKS=172.17.0.0/16
#- EXT_RELAY_HOST=Not_DEFINE
- EXT_RELAY_HOST=relai.fai.com
- OTHER_MY_DEST="localhost.example.com, localhost"
volumes:
- '/tmp/mailrelay/var-spool-postfix:/var/spool/postfix'
# ports:
# - "IP_INTERNE:25:25"
Line by line:
mailrelay:: this is the name of the service, when we will use several container for the management of a service we will see more the utility it is kept for later: D
images: srvmailrelay: This indicates which docker image is used, if we use an available images in a registry the system will automatically download it to start it
- restart: unless-stopped: restart, if the docker service restarts (see a reboot server), this container must be restarted, with this parameter it will automatically restart unless it was stopped manually beforehand.
hostname: ‘relais.example.com’: the name of the “machine” (hostname)
environment:: this defines a list of environment variables that will be passed to the container during initialization, as you can see there is more variable here, this is because my docker image is slightly different: P .
– RELAY_HOST_NAME=monServerDeRelaisInterne.example.com
– ACCEPTED_NETWORKS=172.17.0.0/16
– EXT_RELAY_HOST=relai.fai.com
volumes:: defines the list of directories that will be linked between the local machine AND the container
In the present situation I take the directory / var / spool / postfix why? The goal is to keep emails that are waiting if I recreate the container. : D
ports: Allows the association to be used for the use of the docker-proxy to provide the service on the networks external to the machine.
Sounds good, we try it?
I move to the directory where the docker-compose.yml file is located:
$ ls
bin conf docker-compose.yml Dockerfile README.md run.sh
$ docker-compose up
Creating mailrelay
Attaching to mailrelay
mailrelay | /usr/lib/python2.7/dist-packages/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.
mailrelay | 'Supervisord is running as root and it is searching '
mailrelay | 2017-01-25 16:42:25,336 CRIT Supervisor running as root (no user in config file)
mailrelay | 2017-01-25 16:42:25,336 WARN Included extra file "/etc/supervisor/conf.d/supervisor-all.conf" during parsing
mailrelay | 2017-01-25 16:42:25,351 INFO RPC interface 'supervisor' initialized
mailrelay | 2017-01-25 16:42:25,351 CRIT Server 'unix_http_server' running without any HTTP authentication checking
mailrelay | 2017-01-25 16:42:25,351 INFO supervisord started with pid 1
mailrelay | 2017-01-25 16:42:26,354 INFO spawned: 'postfix' with pid 11
mailrelay | 2017-01-25 16:42:26,358 INFO spawned: 'rsyslogd' with pid 12
mailrelay | 2017-01-25 16:42:27,397 INFO success: rsyslogd entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
mailrelay | 2017-01-25 16:42:29,909 INFO success: postfix entered RUNNING state, process has stayed up for > than 3 seconds (startsecs)
By default the docker-compose up command starts the container in the foreground (forground) we will have to add the -d option so that it is in the background
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aed60f4756cf srvmailrelay "/root/run.sh" 19 seconds ago Up 18 seconds 25/tcp mailrelay
Validation of the configuration:
$ docker exec -it mailrelay bash
root@relais:/# cat /etc/postfix/main.cf
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = $myorigin
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = monServerDeRelaisInterne.example.com
mydestination = monServerDeRelaisInterne.example.com
relayhost = relai.fai.com
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.17.0.0/16
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = ipv4
root@relais:/#
As they say, it’s beautiful: D