Skip to content

Commit

Permalink
Added HAProxy to docker-compose to handle SSL termination. Additional…
Browse files Browse the repository at this point in the history
… fixed to support SENDY_PROTOCOL environment variable.
  • Loading branch information
bubbajames-docker committed Aug 1, 2020
1 parent d1b3812 commit b5b69cf
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 37 deletions.
33 changes: 23 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
# Docker with Sendy Email Campaign Marketing
#
# Build:
# $ docker build -t sendy:latest -f ./Dockerfile .
# $ docker build -t sendy:latest --target sendy -f ./Dockerfile .
#
# Build w/ XDEBUG installed
# $ docker build -t sendy:debug-latest --target debug -f ./Dockerfile .
#
# Run:
# $ docker run --rm -d sendy:latest

FROM php:7.4.8-apache
# $ docker run --rm -d --env-file sendy.env sendy:latest

FROM php:7.4.8-apache as sendy
ARG SENDY_VER=4.1.0

ENV SENDY_VERSION ${SENDY_VER}
Expand All @@ -31,6 +33,7 @@ RUN unzip /tmp/sendy-${SENDY_VER}.zip -d /tmp \
&& chmod 777 /tmp/sendy/uploads \
&& rm -rf /var/www/html \
&& mv /tmp/sendy /var/www/html \
&& mv /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini \
&& rm -rf /tmp/* \
&& echo "\nServerName \${SENDY_FQDN}" > /etc/apache2/conf-available/serverName.conf \
# Ensure X-Powered-By is always removed regardless of php.ini or other settings.
Expand All @@ -47,12 +50,22 @@ RUN a2enmod rewrite headers
# Copy hello-cron file to the cron.d directory
COPY cron /etc/cron.d/cron
# Give execution rights on the cron job
RUN chmod 0644 /etc/cron.d/cron
# Apply cron job
RUN crontab /etc/cron.d/cron
# Create the log file to be able to run tail
RUN touch /var/log/cron.log
RUN chmod 0644 /etc/cron.d/cron \
# Apply cron job
&& crontab /etc/cron.d/cron \
# Create the log file to be able to run tail
&& touch /var/log/cron.log

COPY artifacts/docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["apache2-foreground"]
CMD ["apache2-foreground"]

#######################
# XDEBUG Installation
#######################
FROM sendy as debug
# Install xdebug extension
RUN pecl channel-update pecl.php.net \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
&& rm -rf /tmp/pear
47 changes: 31 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ $ docker run -d \
```
... where `sendy` is the name you want to assign to your container, `campaigns.example.com` is your FQDN of Sendy server, `db_sendy` is your database server instance, `db_password` is the database user's password and `tag` is the tag specifying the Sendy version you want. See the list of tags above.

## Environment Varaibles
## Environment Varaibles
### `SENDY_PROTOCOL` (Optional)
HTTP protocol used in Sendy APP_PATH (`http` or `https`). Default: `http`
### `SENDY_FQDN` (required)
The fully qualified domain name of your Sendy installation. This must match the FQDN associated with your license. You can [purchase a license here](https://sendy.co/?ref=Hcurv).
### `MYSQL_HOST` (required)
Expand All @@ -50,7 +52,7 @@ Database user. Default: `sendy`.
Database user's password. Not recommended for sensitive data! (see: Docker Secrets)

## Docker Secrets
As an alternative to passing sensitive information via environment variables, `_FILE` may be appended to the previously listed environment variables, causing the initialization script to load the values for those variables from files present in the container. In particular, this can be used to load passwords from Docker secrets stored in /run/secrets/\<secret_name> files.
As an alternative to passing sensitive information via environment variables, `_FILE` may be appended to the previously listed environment variables, causing the initialization script to load the values for those variables from files present in the container. In particular, this can be used to load passwords from Docker secrets stored in /run/secrets/\<secret_name> files. (See [repository](https://github.com/bubbajames-docker/sendy) for sample secrets)

For example:

Expand All @@ -64,13 +66,14 @@ Pretty minimalistic `Dockerfile` as everything you need is already bundled. Jus
```dockerfile
FROM bubbajames/sendy:4.1

# ... whatever you want here.
# ... additional apache/php configurations here ...
# e.g. copy your SSL Certiciate and apache configurations if not using load balancer.
```
### Start a Sendy instance
The following starts an instance specifying an environment file.

```console
$ docker run -d -name sendy --env_file sendy.env sendy
$ docker run -d -name sendy --env_file sendy.env -p 80:80 sendy
```

### Sample environment file
Expand All @@ -84,9 +87,9 @@ MYSQL_PASSWORD_FILE=/run/secrets/db_password
```

## Using `docker-compose`
Starts a Send instance and a MySQL database instance with mounted volume for persisted data between restarts. Also uses Docker Secrets to avoid exposing sensitive data via 'inspect'.
Starts an HAProxy load balancer instance for ssl termination, a Sendy instance and a MySQL database instance with mounted volume for persisted data between restarts. Also uses Docker Secrets to avoid exposing sensitive data via 'inspect'.

The following `docker-compose.yml` is also available from image [repository](https://raw.githubusercontent.com/bubbajames-docker/sendy/master/docker-compose.yml).
The latest `docker-compose.yml` and sample files are available from image [repository](https://github.com/bubbajames-docker/sendy). It is highly adviced to clone this repository to ensure latest samples are used.

```yaml
version: "3.7"
Expand All @@ -110,11 +113,10 @@ services:
hostname: db_sendy
container_name: db_sendy
image: mysql:5.6
env_file:
- sendy.env
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: sendy
MYSQL_USER: sendy
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_root_password
- db_password
Expand All @@ -127,20 +129,33 @@ services:
container_name: sendy
depends_on:
- db_sendy
image: sendy:latest
image: sendy:4.1.0
build:
context: .
environment:
SENDY_FQDN: campaigns.example.com
MYSQL_HOST: db_sendy
MYSQL_DATABASE: sendy
MYSQL_USER: sendy
MYSQL_PASSWORD_FILE: /run/secrets/db_password
# Uncomment to enabled XDEBUG build
# target: debug
env_file:
- sendy.env
secrets:
- db_password
ports:
- 8080:80

# Load Balancer: HAProxy
load-balancer:
hostname: lb_sendy
container_name: lb_sendy
image: lb_sendy
build:
context: .
dockerfile: haproxy/Dockerfile
env_file:
- sendy.env
ports:
- 80:80
- 443:443
```
### Start the services
```console
$ docker-compose up -d
Expand Down
1 change: 1 addition & 0 deletions artifacts/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ _is_sourced() {
docker_setup_env() {
log_info "Initializing environment variables..."
# Initialize values that might be stored in a file
file_env 'SENDY_PROTOCOL' 'http'
file_env 'SENDY_FQDN'
file_env 'MYSQL_HOST'
file_env 'MYSQL_DATABASE' 'sendy'
Expand Down
2 changes: 1 addition & 1 deletion cron
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
*/1 * * * * curl -i http://localhost/autoresponders.php >> /var/log/cron.log 2>&1

# import csv
*/1 * * * * curl -i http://localhost/import-csv.php >> /var/log/cron.log 2>&1
*/1 * * * * curl -i http://localhost/import-csv.php >> /var/log/cron.log 2>&1
30 changes: 20 additions & 10 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ services:
hostname: db_sendy
container_name: db_sendy
image: mysql:5.6
env_file:
- sendy.env
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: sendy
MYSQL_USER: sendy
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_root_password
- db_password
Expand All @@ -39,14 +38,25 @@ services:
image: sendy:latest
build:
context: .
environment:
SENDY_FQDN: campaigns.example.com
MYSQL_HOST: db_sendy
MYSQL_DATABASE: sendy
MYSQL_USER: sendy
MYSQL_PASSWORD_FILE: /run/secrets/db_password
XDEBUG_CONFIG: remote_host=host.docker.internal remote_port=9002 remote_enable=1 remote_autostart=1
# Uncomment to enabled XDEBUG build
# target: debug
env_file:
- sendy.env
secrets:
- db_password
ports:
- 8080:80

# Load Balancer: HAProxy
load-balancer:
hostname: lb_sendy
container_name: lb_sendy
image: lb_sendy
build:
context: .
dockerfile: haproxy/Dockerfile
env_file:
- sendy.env
ports:
- 80:80
- 443:443
18 changes: 18 additions & 0 deletions haproxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Application Load Balancer Dockerfile (Development Use Only)
#
# Build:
# $ docker build -t lb_sendy -f haproxy/Dockerfile .
#
# Run:
# $ docker run --rm -d -p 80:80 -p 443:443 lb_sendy

FROM haproxy:2.1-alpine
EXPOSE 80 443

COPY haproxy/haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
COPY haproxy/generateSSLCertificate.sh /usr/local/bin

COPY haproxy/docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["haproxy", "-f", "/usr/local/etc/haproxy/haproxy.cfg"]
28 changes: 28 additions & 0 deletions haproxy/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
set -e

# if necessary, generate and install certificate.
if [ ! -f "/etc/cert/server.pem" ]; then
echo "Generating certificate for domain: $SENDY_FQDN"
echo "Installing OpenSSL"
apk --update-cache add openssl && \
rm -rf /var/cache/apk/*
/usr/local/bin/generateSSLCertificate.sh $SENDY_FQDN
else
echo "Certificate exists for domain: $SENDY_FQDN"
fi

# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
set -- haproxy "$@"
fi

if [ "$1" = 'haproxy' ]; then
shift # "haproxy"
# if the user wants "haproxy", let's add a couple useful flags
# -W -- "master-worker mode" (similar to the old "haproxy-systemd-wrapper"; allows for reload via "SIGUSR2")
# -db -- disables background mode
set -- haproxy -W -db "$@"
fi

exec "$@"
54 changes: 54 additions & 0 deletions haproxy/generateSSLCertificate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/sh

# Lifted from https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/
# Author: Brad Touesnard

if [ "$#" -ne 1 ]
then
echo "Usage: Must supply a domain"
exit 1
fi

DOMAIN=$1

echo "Generating Development RootCA and SSL Certificate for: $DOMAIN"

# create output directory
mkdir /etc/cert

# create temporary work directory
mkdir /tmp/deleteme
cd /tmp/deleteme

# Create RootCA key and cert
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1825 \
-subj "/C=US/ST=CA/L=/O=/OU=/CN=rootCA-$DOMAIN" -out ./rootCA.crt



# Create domain certificate signing request
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/C=US/ST=CA/L=/O=/OU=/CN=$DOMAIN" -out server.csr

cat > $DOMAIN.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = $DOMAIN
EOF

openssl x509 -req -in server.csr -CA ./rootCA.crt -CAkey ./rootCA.key -CAcreateserial -days 825 -sha256 \
-extfile $DOMAIN.ext -out ./server.crt

cat ./server.key ./server.crt > /etc/cert/server.pem
cat ./rootCA.key ./rootCA.crt > /etc/cert/rootCA.pem

cd /

# remove temporary work directory
rm -rf /tmp/deleteme

echo "Development RootCA and SSL Certificate are located in /etc/cert/"
50 changes: 50 additions & 0 deletions haproxy/haproxy.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
global
maxconn 100
# Log to stdout at info level
log stdout daemon info

# SSL Params
tune.ssl.default-dh-param 2048
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets


defaults
# Use Log settings from global section
log global
# HAProxy in HTTP mode
mode http
# Log standard http fields
option httplog
# Don't log null responses
option dontlognull
# Timeouts
timeout connect 5s
timeout client 30s
timeout server 30s


frontend nonSecureFrontend
bind :80
# Redirect http to https
redirect scheme https code 301


frontend secureFrontend
# primary cert is /etc/cert/server.pem
bind :443 ssl crt /etc/cert/server.pem

# Add X-Forwarded-For and X-Forwarded-Port headers
option forwardfor

# All others use webBackend
use_backend webBackend


backend webBackend
# Health checks using HTTP GET / and expects 200 OK response
option httpchk GET /
http-check expect status 200
# Health check interval 60sec, if server is down, check every 5secs,
# Server is marked down after 12 failed attempts (eg 1min),
# Server is healthly after 1 success response.
server webApp1 sendy:80
15 changes: 15 additions & 0 deletions sendy.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
SENDY_PROTOCOL=https
# Update FQDN to match your Sendy licensed domain.
SENDY_FQDN=campaigns.example.com
MYSQL_HOST=db_sendy
MYSQL_DATABASE=sendy
MYSQL_USER=sendy
MYSQL_PASSWORD_FILE=/run/secrets/db_password
# MYSQL_PASSWORD=db_password

# XDEBUG
# REQUIRES BUILDING image with XDEBUG installed.
# See Dockerfile header for details.

# Uncomment to set XDEBUG enviorment configuration. Ensure `remote_port` does not have conflicts.
#XDEBUG_CONFIG=remote_host=host.docker.internal remote_port=9000 remote_enable=1 remote_autostart=1

0 comments on commit b5b69cf

Please sign in to comment.