Install Nginx In Docker Container

/ Comments off

Related Articles

The Docker way to do this would be to have two containers for the two services. Docker-compose is a tool that helps manage multi-container applications. The following docker-compose.yml should work for you. Step 6 - Run Docker Compose. At this, point the docker-compose.yaml file is ready to host an Nginx server. You can now use docker-compose up command to bring your environment up. Navigate to the project directory and run Docker Compose with the following command: cd project docker-compose up -d.

Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt. Docker can be used to install nginx and php5 (php-mpm) in a ubuntu container. We’ll cover docker running on Mac and Ubuntu Linux for the purpose of this tutorial. The steps are mostly same for both environments. Download ubuntu image and run it. We’ll use -p 8081:80 when running docker container to make its port 80 available on host port 8081.

Nginx is a great piece of software that allows you to easily wrap your application inside a reverse-proxy, which can then handle server-related aspects, like SSL and caching, completely transparent to the application behind it.

Install Nginx In Docker Container

Introduction

Some aspects of web applications, like SSL encryption, request caching and service discovery can be managed outside of the application itself. Reverse-proxies like Nginx can handle many of those responsibilities, so we as developers don't have to think about it in our software.

Additionally, some software is not meant to be available over the internet, since the don't have proper security measures in place. Many databases are like that. And it is good practice in general to not make internal services public-facing that don't have to be.

Install Nginx In Docker Container

All of that can be achieved with docker-compose and Nginx.

docker-compose

docker-compose is a neat little tool that lets you define a range of docker containers that should be started at the same time, and the configuration they should be started with. This includes the exported ports, the networks they belong to, the volumes mapped to it, the environment variables, and everything else that can be configured with the docker run command.

In this section I'll briefly explain how to configure the docker-compose features used in this article. For more details take a look at the documentation.

The main entry point is a docker-compose.yml file. It configures all aspects of the containers that should be started together.

Here is an example docker-compose.yml:

As you can see, there are 2 images specified.
First nginx, with the name production_nginx. It specifies a volume that replaces the default Nginx configuration file. Also a mapping of the host's ports 80 and 443 to the container's ports 80 and 443 is defined.
The second image is one is one I created myself. It exposes port 80. The difference to the ports configuration is that they are not published to the host machine. That's why it can also specify port 80, even though nginx already did.

There are a few other configuration options used in this article, specifically networks, volumes and environment variables.

Networks

With networks it is possible to specific which containers can talk to each other. They are specified as a new root config entry and on the container configurations.

In the root object networks, the network my-network-name is defined. Each container is assigned to that network by adding it to the network list.

If no network is specified, all containers are in the same network, which is created by default. Therefore, if only one network is used, no network has to be specified at all.

A convenient feature of networks is that containers in the same one can reference each other by name. In the example above, the url http://ismydependencysafe will resolve to the container ismydependencysafe.

Volumes

Volumes define persistent storage for docker containers. If an application writes somewhere no volume is defined, that data will be lost when the container stops.

There are 2 types of volumes. The ones that map a file or directory to one inside the container, and the ones that just make a file or directory persistent (named volumes), without making them accessible on the file system (of course they are somewhere, but that is docker implementation specific and should not be meddled with).

Install nginx on docker containerInstall Nginx In Docker Container

The first type, volumes that map a specific file or directory into the container, we have already seen in the example above. Here is it again, with an additional volume that also specifies a directory in the same way:

Named volumes are specified similar to networks, as a separate root configuration entry and directly on the container configuration.

Environment Variables

Docker can also specify environment variables for the application in the container. In the compose config, there are multiple ways to do so, either by specifying a file that contains them, or declaring them directly in docker-compose.yml.

As you can see, both ways can also be used at the same time. Just be aware that variables set in environment overwrite the ones loaded from the files.

The environment files must have the format VAR=VAL, one variable on each line.

CLI

The commands for starting and stopping the containers are pretty simple.

To start use docker-compose up -d.
The -d specifies that it should be started in the background. Without it, the containers would be stopped when the command line is closed.

To stop use docker-compose down.

Both commands look for a docker-compose.yml file in the current directory. If it is somewhere else, specify it with -f path/to/docker-compose.yml.

Now that the basics of docker-compose are clear, lets move on to Nginx.

Nginx

Nginx is a web server with a wide array of features, including reverse proxying, which is what it is used for in this article.
It is configured with a nginx.conf. By default it looks for it in /etc/nginx/nginx.conf, but it is of course possible to specify another file.

As a reverse proxy, it can transparenty handle two very important aspects of a web application, encryption and caching. But before going into detail about that, lets see how the reverse proxy feature itself is configured:

The Nginx config is organized in contexts, which define the kind of traffic they are handling. The http context is (obviously) handling http traffic. Other contexts are mail and stream.

The server configuration specifies a virtual server, where each can have its own rules. The server_name directive defined which urls or IP addresses the virtual server responds to.

The location configuration defines where to route incoming traffic. Depending on the url, the requests can be passed to one service or another. In the config above, the start of the route specifies the service.
proxy_pass sets the new url, and with rewrite the url is rewritten so that it fits the service. In this case, the yourService{x} is removed from the url.

This was a general overview, later sections will explain how caching and SSL can be configured.

For more details, check out the docs.

Now that we know the pieces, lets start putting them together.

Setup Nginx as a Reverse-Proxy inside Docker

For a basic setup only 3 things are needed:

1) Mapping of the host ports to the container ports2) Mapping a config file to the default Nginx config file at /etc/nginx/nginx.conf3) The Nginx config

Install Nginx In Docker Container Code

In a docker-compose file, the port mapping can be done with the ports config entry, as we've seen above.

The mapping for the Nginx config is done with a volume, which we've also seen before:

The Nginx config is assumed to be in the same directory as docker-compse.yml (./nginx.conf), but it can be anywhere of course.

Cache Configuration

Inside

Adding caching to the setup is quite easy, only the Nginx config has to be changed.
In the http context, add a proxy_cache_path directive, which defines the local filesystem path for cached content and name and size of the memory zone.
Keep in mind though that the path is inside the container, not on the host's filesystem.

In the server or location context for which responses should be cached, add a proxy_cache directive specifying the memory zone.

That's enough to define the cache with the default caching configuration. There are a lot of other directives which specify which responses to cache in much more detail. For more details on those, have a look at the docs.

Securing HTTP Traffic with SSL

Install Nginx In Docker Container Design

By now the server setup is finished. docker-compose starts up all containers, and the Nginx container acts as a reverse-proxy for the services. There is just one thing left to set up, as this site so beautifully explains, encryption.

To install certbot, the client that fetches certificates from Let’s Encrypt, follow the install instructions.

Generating SSL Certificates with certbot

certbot has a variety of ways to get SSL certificates. There are plugins for widespread webservers, like Apache and Nginx, one to use a standalone webserver to verify the domain, and of course a manual way.

We'll use the standalone plugin. It starts up a separate webserver for the certificate challenge, which means the port 80 or 443 must be available. For this to work, the Nginx webserver has to be shut down, as it binds to both ports, and the certbot server needs to be able to accept inbound connections on at least one of them.

To create a certificate, execute

and follow the instructions. You can also create a certificate for multiple urls at once, by adding more -d parameters, e.g. -d your.server1.url-d your.server2.url.

Automating Certificate Renewal

The Let's Encrypt CA issues short-lived certificates, they are only valid for 90 days. This makes automating the renewal process important. Thankfully, certbot makes that easy with the command certbot renew. It checks all installed certificates, and renews the ones that will expire in less than 30 days.

It will use the same plugin for the renewal as was used when initially getting the certificate. In our case that is the standalone plugin.

The challenge process is the same, so also for renewals the ports 80 or 443 must be free.
certbot provides pre and post hooks, which we use to stop and start the webserver during the renewal, to free the ports.
The hooks are executed only if a certificate needs to be renewed, so there is no unnecessary downtime of your services.

Since we are using docker-compose, the whole command looks like this:

To complete the automation simply add the previous command as a cronjob.
Open the cron file with crontab -e.
In there add a new line with

That's it. Now the renew command is executed daily, and you won't have to worry about your certificates' expiration date.

Using the Certificates in the Nginx Docker Container

By now the certificates are requested and stored on the server, but we don't use them yet. To achieve that, we have to

1) Make the certificates available to the Nginx container and2) Change the config to use them

To make the certificates available to the Nginx container, simply specify the whole letsencrypt directory as a volume on it.

Adapting the config and making it secure is a bit more work.By default, a virtual server listens to port 80, but with SSL, it should also listen to port 443. This has to be specified by 2 listen directives.
Additionally, the certificate must be defined. This is done with the ssl_certificate and ssl_certificate_key directives.

These small changes are enough to configure nginx for SSL.
It uses the default SSL settings of Nginx though, which is ok, but can be improved upon.

Improving Security of Nginx Config

Install Nginx In Docker Container List

At the beginning of this section I should mention that, if you use the latest version of nginx, its default SSL settings are secure. There is no need to define the protocols, ciphers and other parameters.

That said, there are a few SSL directives with which we can improve security even further.
Just keep in mind that by setting these, you are responsible for keeping them up to date yourself. The changes Nginx does to the default config settings won't affect you, since you're overwriting them.

First, set

This disables all SSL protocols and TLSv1.0, which are considered insecure (TLSv1.0, SSLv3, SSLv2). TLSv1.1 and TLSv1.2 are, at the time of writing (July 2018), considered secure, but nobody can promise that they will not be broken in the future.

Next, set

The ciphers define how the encryption is done. Those values are copied from this article, as I'm not an expert in this area.

Those are the most important settings. To improve security even more, follow these articles:

You can check the security of your SSL configuration with a great website SSL Labs provides.

Wrap up

In this article we've covered how to setup docker-compose, use its network and volume feature and how to set environment variables, how to use Nginx as a reverse proxy, including caching and SSL security. Everything that's needed to host a project.

Just keep in mind that this is not a terribly professional setup, any important service will need a more sophisticated setup, but for small projects or side-projects it is totally fine.

Amendment

Here are the resulting nginx.conf and docker-compose.yml files. They include placeholder names, urls and paths for your applications.

Install Nginx On Docker Container

docker-compose.yml

nginx.conf

Install Nginx In Docker Containers

Follow me onTwitterfor more of my thoughts, articles, projects and work.