Running Web Service With Docker
by Thomas Urban
Developing web applications using docker containers is pretty common today. So is continuously deploying it to servers for testing, staging and releasing. By using docker containers continuously deploying your work can be automated. However, in public there are additional requirements to be met. The most important one is supporting encrypted requests via HTTPS. You might take care of this in your web application or leave this to the hosting of a released version of your application. This post is about a template for achieving the latter.
Our proposed setup consists of three containers:
One container is called app and it is equivalent to the container you used to work with while developing the application. Thus it is expected to handle web requests on port 80, only.
Just remember, that with Docker this equivalency applies to the container's structure due to relying on the same image. They are different instances probably affected by different environment variables in different ways. Nonetheless the high similarity supports continuous integration efforts and how much they can tell about whether some application is going to work in a public setup or not using unit tests and E2E tests.
A second container is called proxy. This one is basically containing a web server which is configured to handle requests on both ports 80 and 443 to support unencrypted requests via HTTP (port 80) and encrypted requests via HTTPS (port 443). In both cases requests are basically forwarded unencryptedly to port 80 of the former container called app.
The third container isn't exposing any network ports but contains the client required for automatically fetching a free certificate from LetsEncrypt service. A certificate is required for supporting encrypted access via HTTPS. Using LetsEncrypt is just one option of several available, but this one is preferred for two reasons:
- Fetching certificates from LetsEncrypt can be automated. You don't need any manual interaction except for the initial setup for keeping your certificate up-to-date.
- Certificates issued by LetsEncrypt are valid for a short term of 90 days, only. This is improving security in case of your previously retrieved certificate is relying on compromised signing certificates which are replaced by the certificating authority which is LetsEncrypt in this case. By replacing certificates more often such vulnerable certificates won't be found in the wild quite as long as with other commercial setups.
Basically you need a host capable of running docker containers. This might be a virtual server running Linux. In our case we used to work with Ubuntu-based servers. In addition this template relies on docker-compose which is a tool managing compositions of containers usually called a service,
This post doesn't cover tutorial on how to install docker and docker-compose under Ubuntu, though. According to some issues in Ubuntu 18.04 LTS we currently advise to stick with the installation instructions provided by the Docker community itself.
Next you need a copy of the project on GitHub. You can download an archive from GitHub or clone the project using
Setting Up Service
All further steps assume you are currently working in root folder of project downloaded or cloned before.
Copy file .env.dist to .env and open the latter in a text editor for adjusting variables to match your needs.
DOMAIN is providing the domain name of your service in public. It is used for requesting a matching certificate to be used on handling encrypted requests. Due to validation process for issuing the certificate you need to make sure the selected domain name is actually referring to the server which is hosting the docker service, a.k.a. the server you are currently editing this variable in a file.
APPLICATION is the name of a container image to be fetched and pulled. It is expected to contain the actual application to be provided. And it is used to create an instance becoming the container called app in description above.
Next start the proxy service but keep support for encrypted requests disabled since there isn't any certificate, yet. We are going to fetch it now.
NOSSL=1 docker-compose up -d proxy
When proxy is running the letsencrypt container is capable of passing validation of selected domain. Thus, fetch the certificate by running:
docker-compose run --rm letsencrypt initialize
On successful retrieval shut down the proxy container running in background:
Initial setup is finished now.
Starting the Service
Start the whole service now supporting encrypted requests as well:
docker-compose up -d
Logs of service are available using
This is merging logs of all containers included with service. You might want to focus on a single container's logs by providing its name in addition:
docker-compose logs app
Important: Renewal of Certificate
The letsencrypt container doesn't take care of frequently updating the certificate for you due to prevent it from running needlessly most of the time. You should create a cron job on your host - e.g. by running
crontab -e as root - invoking the following command once a day:
cd /path/to/the/project && docker-compose run --rm letsencrypt renew && docker-compose kill -s SIGHUP proxy
This command consists of three parts:
- The command is switching to the folder containing the project you've downloaded or clone before. Replace the path name with the one matching your actual setup.
- Following the first two ampersands the second part is starting letsencrypt container for trying to trigger renewal of certificate. This will "fail" most of the time due to preventing frequent interaction with the services of LetsEncrypt. That's okay. But every few weeks this command will fetch an updated certificate. Keep track of this for a while.
- The last part following another set of two ampersands is signalling the proxy container to reload its configuration without restarting. This will make sure an actually updated certificate will be instantly used by that container, too, without interfering with any running request.
This template provides a very simple and convenient way for running your web application on any hardware supporting docker. If you find a bug or have some advises feel free to open an issue at GithHub.