This article is about:
- setting up an Apache Webserver in an Alpine Linux Docker Container
- setting up a SSL encryption via Let's Encrypt
Requirements:
- Basic understanding of docker and docker-compose
- Basic understanding of Apache
Structure of Blog Article:
- My Motivation for Docker usage and this article
- Temporary Apache Container for handshaking with Let's Encrypt, a free SSL certificate service
- Getting the Certificates via Certbot Docker Container
- Spinning up the Production Apache Server
For my website consisting of a blog and some webapplications I would like to migrate the existing application logic and static files into seperated docker containers to streamline the development process, the testing and the operation of the production system Docker allows to isolate parts of my website into decoupled units which can be treated seperately from each other. I would then be able to try out smaller technical adjustments on specific parts, such as security adjustments . In the past it happened that e.g. a change of filters in mod_security lead to negative sideeffects on a app that I host (with target of 24/7 availablity), whereas the setting was intended to harden the blogging part of my website. One of the huge advantages that comes with Microservices and Dockerization is that you are much more flexible in your sourcing decisions. In my private context this means I can today host my website in the Tencent Cloud, but would be able to easily shift a single service or the whole website to another supplier, such as Amazon Web Services when I move back to Europe.
I found this perfect Article on setting up everything for Nginx . The motivation to write this article is mainly driven for documentation reasons. What's more: It seems like there is no tutorial for doing such kind of steps for Apache based webservers. I suggest you read through his article for the background information and just come back here, if you want to set up everything for Apache.
The temporary Apache has the only purpose for exposing a temporary website over http / port 80. This website will be used by the certbot and the triggered ACME-Challenge later. For preparing it you need those files:
index.html (click to see the content)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Temporary Webserver</title>
<style>
body {
margin: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
height: 100vh;
align-items: center;
}
</style>
</head>
<body>
<h1>This is the webserver for the Acme-Challenge</h1>
</body>
</html>
Dockerfile (click to see the content)
FROM alpine:latest
LABEL author="Leon Sczepansky"
ENV server_name=localhost
RUN apk add --no-cache apache2
RUN rm -rf /var/www/localhost/cgi-bin/
CMD exec /usr/sbin/httpd -D FOREGROUND -f /etc/apache2/httpd.conf
Configfile httpd.conf (click to see the content)
ServerRoot /var/www
LoadModule mpm_prefork_module modules/mod_mpm_prefork.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
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule rewrite_module modules/mod_rewrite.so
Listen 80
<IfModule unixd_module>
User apache
Group apache
</IfModule>
ServerName ${server_name}
ServerAdmin [email protected]
ServerTokens Prod
ServerSignature Off
DocumentRoot "/var/www/localhost/htdocs"
<Directory /.well-known/acme-challenge>
Allow from all
</Directory>
<Directory "/var/www/localhost/htdocs">
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog logs/error.log
LogLevel info
Composefile docker-compose.yml (click to see the content)
version: '3.7'
services:
le-apache:
container_name: 'le-apache'
image: lets-encrypt-apache:latest
ports:
- "80:80"
volumes:
- ./httpd.conf:/etc/apache2/httpd.conf
- ./html:/var/www/localhost/htdocs/
networks:
- docker-network
networks:
docker-network:
driver: bridge
The folder tree should look like this:
├── letsencrypt
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── html
│ │ └── index.html
│ └── httpd.conf
...
For the following commands I assume you run them while beeing in the ./letsencrypt
folder.
- Build the webserver via
docker build -t lets-encrypt-apache .
- Start it via
docker-compose up -d
- When your webserver is running, start the certbot docker (from dockerhub)
sudo docker run -it --rm -v $PWD/volumes/etc/letsencrypt:/etc/letsencrypt -v $PWD/volumes/var/lib/letsencrypt:/var/lib/letsencrypt -v $PWD/html:/data/letsencrypt -v $PWD/volumes/var/log/letsencrypt:/var/log/letsencrypt certbot/certbot certonly --webroot --email [email protected] --agree-tos --no-eff-email --webroot-path=/data/letsencrypt -d fundapp.fundassa.org
- After you got the success message in your command line's output you stop the temporary apache via
docker-compose down
On the same hierarchy level like the letsencrypt folder I will now create a folder for the productive Apache Server (as also suggested in the mentioned nginx article).
.
├── letsencrypt/...
├── production
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── html
│ │ └── index.html
│ └── httpd.conf
Whereras the temporary server just had the purpose for getting the certificates, the production server will serve the website. It will run 24/7 and uses HTTPS only.
Dockerfile
FROM alpine:latest
LABEL author="Leon Sczepansky"
ENV server_name=localhost
RUN apk add --no-cache apache2-ssl
RUN rm -rf /var/www/localhost/cgi-bin/
CMD exec /usr/sbin/httpd -D FOREGROUND -f /etc/apache2/httpd.conf
docker-compose.yml
version: '3.7'
services:
productive-apache:
container_name: 'productive-apache'
image: productive-apache:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./httpd.conf:/etc/apache2/httpd.conf
- ./html:/var/www/localhost/htdocs/
- /docker-volumes/etc/letsencrypt/live/magicpony.de/cert.pem:/etc/letsencrypt/live/magicpony.de/cert.pem
- /docker-volumes/etc/letsencrypt/live/magicpony.de/fullchain.pem:/etc/letsencrypt/live/magicpony.de/fullchain.pem
- /docker-volumes/etc/letsencrypt/live/magicpony.de/privkey.pem:/etc/letsencrypt/live/magicpony.de/privkey.pem
networks:
- docker-network
environment:
- server_name=magicpony.de
networks:
docker-network:
driver: bridge
Httpd.conf
ServerRoot /var/www
LoadModule mpm_prefork_module modules/mod_mpm_prefork.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
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule logio_module modules/mod_logio.so
Listen 80
<IfModule unixd_module>
User apache
Group apache
</IfModule>
ServerName ${server_name}
ServerAdmin [email protected]
ServerTokens Prod
ServerSignature Off
DocumentRoot "/var/www/localhost/htdocs"
IncludeOptional /etc/apache2/conf.d/*.conf
AddDefaultCharset UTF-8
EnableSendfile on
FileETag None
TraceEnable off
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
Header always append X-Frame-Options SAMEORIGIN
Header set X-XSS-Protection "1; mode=block"
RewriteEngine On
RewriteCond %{THE_REQUEST} !HTTP/1.1$
RewriteRule .* - [F]
Timeout 60
<IfModule dir_module>
DirectoryIndex index.html
</IfModule>
<Files ".ht*">
Require all denied
</Files>
<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 "logs/access_log" combined
</IfModule>
ErrorLog logs/error.log
LogLevel info
<VirtualHost *:80>
DocumentRoot "/var/www/localhost/htdocs"
ServerName ${server_name}
ServerAlias www.${server_name}
Alias "/.well-known/acme-challenge" "/data/letsencrypt"
<Directory "/data/letsencrypt">
Options Indexes FollowSymLinks MultiViews
Require all granted
</Directory>
<Directory />
AllowOverride none
Options -Indexes -Includes
Require all granted
<LimitExcept GET POST HEAD>
deny from all
</LimitExcept>
</Directory>
<Directory "/var/www/localhost/htdocs">
AllowOverride None
Options -Indexes -Includes
Require all granted
</Directory>
<Location /status >
SetHandler server-status
</Location>
<Location / >
Redirect / https://${server_name}/
</Location>
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
DocumentRoot "/var/www/localhost/htdocs"
ServerName ${server_name}
ServerAlias www.${server_name}
<Directory />
AllowOverride none
Options -Indexes -Includes
Require all granted
<LimitExcept GET POST HEAD>
deny from all
</LimitExcept>
</Directory>
<Directory "/var/www/localhost/htdocs">
AllowOverride None
Options -Indexes -Includes
Require all granted
</Directory>
<Location /status >
SetHandler server-status
</Location>
ErrorLog logs/error.log
LogLevel info
CustomLog "logs/access_log" combined
SSLEngine On
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder on
SSLOptions +StrictRequire
SSLCertificateFile /etc/letsencrypt/live/${server_name}/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/${server_name}/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/${server_name}/fullchain.pem
</VirtualHost>
</IfModule>
- Lets build the image for the productive apache
docker build -t productive-apache .
- Testrun it via
docker-compose up -d
-> check if both ports are working correctly
- Shut down via
docker-compose down
(if needed) - !!! Don't forget to set up the renewal cron-job as mentioned in the nginx article.
- https://github.com/Spansky/apache-and-letsencrypt (Github Repository)
- https://www.humankode.com/ssl/how-to-set-up-free-ssl-certificates-from-lets-encrypt-using-docker-and-nginx (Tutorial for Nginx/Let's Encrypt in Docker)