Docker is a compelling platform to package and run web applications, especially when paired with one of the many Platform as a Service (PaaS) offerings provided by cloud platforms. NGINX has long provided DevOps teams with the ability to host web applications on Linux, and also provides an official Docker image to use as the base for custom web applications.
In this post, I explain how DevOps teams can use the NGINX Docker image to build and run web applications on Docker.
Getting started with the base image
NGINX is a versatile tool with many uses, including a load balancer, reverse proxy, and network cache. However, when running NGINX in a Docker container, most of these high level functions are delegated to other specialized platforms or other instances of NGINX. Typically, NGINX fulfils the function of a web server when running in a Docker container.
To create an NGINX container with the default web site, run the following command:
docker run -p 8080:80 nginx
This command will download the nginx
image (if it hasn't already been downloaded) and create a container exposing port 80 in the container to port 8080 on the host machine. You can then open http://localhost:8080/index.html
to view the default "Welcome to nginx!" web site.
To allow the NGINX container to expose custom web assets, you can mount a local directory inside the Docker container.
Save the following HTML code to a file called index.html
:
<html>
<body>
Hello from Octopus!
</body>
</html>
Next, run the following command to mount the current directory under /usr/share/nginx/html
inside the NGINX container with read only access:
docker run -v $(pwd):/usr/share/nginx/html:ro -p 8080:80 nginx
Open http://localhost:8080/index.html
again and you see the custom HTML page displayed.
One of the benefits of Docker images is the ability to bundle all related files into a single distributable artifact. To realize this benefit, you must create a new Docker image based on the NGINX image.
Creating custom images based on NGINX
To create your own Docker image, save the following text to a file called Dockerfile
:
FROM nginx
COPY index.html /usr/share/nginx/html/index.html
Dockerfile
contains instructions for building a custom Docker image. Here you use the FROM
command to base your image on the NGINX one, and then use the COPY
command to copy your index.html
file into the new image under the /usr/share/nginx/html
directory.
Build the new image with the command:
docker build . -t mynginx
This builds a new image called mynginx
. Run the new image with the command:
docker run -p 8080:80 mynginx
Note that you didn't mount any directories this time. However, when you open http://localhost:8080/index.html
your custom HTML page is displayed because it was embedded in your custom image.
NGINX is capable of much more than hosting static files. To unlock this functionality, you must use custom NGINX configuration files.
Advanced NGINX configuration
NGINX exposes its functionality via configuration files. The default NGINX image comes with a simple default configuration file designed to host static web content. This file is located at /etc/nginx/nginx.conf
in the default image, and has the following contents:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
There's no need to understand this configuration file in detail, but there is one line of interest that instructs NGINX to load additional configuration files from the /etc/nginx/conf.d
directory:
include /etc/nginx/conf.d/*.conf;
The default /etc/nginx/conf.d
file configures NGINX to function as a web server. Specifically the location /
block loading files from /usr/share/nginx/html
is why you mounted your HTML files to that directory previously:
server {
listen 80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
You can take advantage of the instruction to load any *.conf
configuration files in /etc/nginx
to customize NGINX. In this example you add a health check via a custom location listening on port 90 that responds to requests to the /nginx-health
path with a HTTP 200 OK.
Save the following text to a file called health-check.conf
:
server {
listen 90;
server_name localhost;
location /nginx-health {
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
Modify the Dockerfile
to copy the configuration file to /etc/nginx/conf.d
:
FROM nginx
COPY index.html /usr/share/nginx/html/index.html
COPY health-check.conf /etc/nginx/conf.d/health-check.conf
Build the image with the command:
docker build . -t mynginx
Run the new image with the command. Note the new port exposed on 9090:
docker run -p 8080:80 -p 9090:90 mynginx
Now open http://localhost:9090/nginx-health
. The health check response is returned to indicate that the web server is up and running.
The examples above base your custom images on the default nginx
image. But there are other variants that provide much smaller image sizes without sacrificing any functionality.
Choosing NGINX variants
The default nginx
image is based on Debian. However, NGINX also provides images based on Alpine.
Alpine is frequently used as a lightweight base for Docker images. To view the sizes of Docker images, they must first be pulled down to your local workstation:
docker pull nginx
docker pull nginx:stable-alpine
You can then find the image sizes with the command:
docker image ls
From this you can see the Debian image weighs around 140 MB while the Alpine image weighs around 24 MB. This is quite a saving in image sizes.
To base your images on the Alpine variant, you need to update the Dockerfile
:
FROM nginx:stable-alpine
COPY index.html /usr/share/nginx/html/index.html
COPY health-check.conf /etc/nginx/conf.d/health-check.conf
Build and run the image with the commands:
docker build . -t mynginx
docker run -p 8080:80 -p 9090:90 mynginx
Once again open http://localhost:9090/nginx-health
or http://localhost:8080/index.html
to view the web pages. Everything continues to work as it did previously, but your custom image is now much smaller.
Conclusion
NGINX is a powerful web server, and the official NGINX Docker image allows DevOps teams to host custom web applications in Docker. NGINX also supports advanced scenarios thanks to its ability to read configuration files copied into a custom Docker image.
In this post, you learned how to create a custom Docker image hosting a static web application, added advanced NGINX configuration files to provide a health check endpoint, and compared the sizes of Debian and Alpine NGINX images.
Learn how to use other popular container images:
Resources
Learn more
If you want to build and deploy containerized applications to AWS platforms such as EKS and ECS, try the Octopus Workflow Builder. The Builder populates a GitHub repository with a sample application built with GitHub Actions workflows and configures a hosted Octopus instance with sample deployment projects demonstrating best practices such as vulnerability scanning and Infrastructure as Code (IaC).
Happy deployments!