Containers: Deploying a static website with nginx and Podman

Mythical 5th, February 2024

This document will guide you through the process of creating and running a static HTML website in a container on a personal computer, using Podman for the containerisation and nginx as the web server. The focus is container management, it will not cover how to publish the website to the internet.


Terminology and concept

The traditional way of setting up a web server was to download the webserver executable, then install it, then configure it, and tell it where on the computer your HTML files are located. With containerisation, you download a web server image from a registry, then create your own version of that image with your HTML files and configuration within it, then deploy it to be run in a container by a virtualisation engine such as Docker or Podman.

Explore the Podman environment

Before we start, let's familiarise ourselves with some Podman commands in a Terminal window. We can use the podman system info command [docs] to see how Podman is configured. The entry for registries will be important later.

$ podman system info
[ ...snip ]
[ snip... ]

We can list all our running containers with podman container list[docs], but need to add the --all option to include ones which are not running. We have no containers yet.

$ podman container list --all

Images we download or build for ourselves are listed with podman image list[docs]. This list is also empty.

$ podman image list

Download an nginx image

An image is downloaded with the podman image pull command [docs]. Here, the image name and a version tag are specified—we want the latest version of nginx. Because no registry was named, Podman offers all the registries it has been configured to use. The selected one will be reused automatically the next time an nginx image is pulled.

$ podman image pull nginx:latest
? Please select an image:
Trying to pull
Getting image source signatures
Copying blob 398157bc5c51 done   | 
Copying blob c57ee5000d61 done   | 
Copying blob f24a6f652778 done   | 
Copying blob 9b0163235c08 done   | 
Copying blob 9f3589a5fc50 done   | 
Copying blob f0bd99a47d4a done   | 
Copying blob 1ef1c1a36ec2 done   | 
Copying config b690f5f0a2 done   | 
Writing manifest to image destination

Podman has created a long ID for the image, but the first 4 characters are sufficient to refer to this image in commands for managing it. The list of images will now have something in it!

$ podman image list
REPOSITORY               TAG         IMAGE ID      CREATED       SIZE  latest      b690f5f0a2d5  3 months ago  191 MB

Create a container

To create a container from the pulled image, use podman container create[docs]. Give the container a name, and tell Podman that it should use the nginx image. The --publish 8080:80 option tells Podman to make the container's port 80 available via port 8080 on the host computer.

$ podman container create --name webserver --publish 8080:80 nginx

That's another long ID, and there will be something in the list of containers now.

$ podman container list --all
CONTAINER ID  IMAGE                           COMMAND               CREATED         STATUS      PORTS                 NAMES
8d491547a2f2  nginx -g daemon o...  33 seconds ago  Created>80/tcp  webserver

Start the container

The container can be started with the podman container start command [docs], with the name of the container being specified.

$ podman container start webserver

The list of containers now confirms that the container has been started. Opening http://localhost:8080/ in a web browser will show the nginx Welcome page.

$ podman container list --all
CONTAINER ID  IMAGE                           COMMAND               CREATED         STATUS             PORTS                 NAMES
8d491547a2f2  nginx -g daemon o...  2 minutes ago   Up About a minute>80/tcp  webserver

Build a custom image

Deploying the nginx Welcome page is reassuring but not very practical, so that container is of no use. It needs to be stopped and removed, and then we can deploy an actual website. We need the commands podman container stop[docs] and podman container rm[docs].

$ podman container stop webserver
$ podman container rm webserver
$ podman container list --all

To build a custom image with our own website in it, we need a directory to work in, and a Dockerfile to let the build process know what to do. The Dockerfile is just a file named Dockerfile, and the working directory can be created anywhere. The Dockerfile below will build a new image based on the nginx image, and copy our content subdirectory into a directory within the new image.

Dockerfile commands:

FROM nginx
COPY content /usr/share/nginx/html

Our working directory contents:

$ tree
├── content
│   └── index.html
└── Dockerfile

2 directories, 2 files

The podman build command [docs] uses --tag as the option for naming the resulting image. The . denotes the directory the build will run in—our working directory. When the build is complete, the new image appears in the image list; its repository is localhost.

$ podman build --tag my_nginx .
STEP 1/2: FROM nginx
STEP 2/2: COPY content /usr/share/nginx/html
COMMIT my_nginx
--> 4e619540f3c4
Successfully tagged localhost/my_nginx:latest
$ podman image list
REPOSITORY               TAG         IMAGE ID      CREATED         SIZE
localhost/my_nginx       latest      4e619540f3c4  22 seconds ago  192 MB  latest      b690f5f0a2d5  3 months ago    191 MB

Deploying the image in a container is the same process as before, but the new image must be specified instead of nginx.

$ podman container create --name webserver --publish 8080:80 my_nginx
$ podman container list --all
CONTAINER ID  IMAGE                      COMMAND               CREATED         STATUS      PORTS                 NAMES
9403862d3199  localhost/my_nginx:latest  nginx -g daemon o...  15 seconds ago  Created>80/tcp  webserver
$ podman container start webserver
$ podman container list --all
CONTAINER ID  IMAGE                      COMMAND               CREATED         STATUS        PORTS                 NAMES
9403862d3199  localhost/my_nginx:latest  nginx -g daemon o...  37 seconds ago  Up 5 seconds>80/tcp  webserver

Opening http://localhost:8080/ in a web browser now shows your own website. The nginx Welcome page might have been cached by your browser, in which case you'll need to do a hard refresh: Ctrl+Shift+R.

Other Approaches

The method above is satifyingly simple, once you have learned the basics: the base image contains everything in your project or application which is constant, and the build process handles anything which can change from version to version.

If you need to alter the default setup of nginx, you might choose to add a line to your Dockerfile to copy configuration files in each build. But if the setup does not often change, you could create a new base image with the configuration files already in it, and build your website from that. To remove the default nginx configuration files, you would use RUN in your Dockerfile to run the rm command within the image, before using COPY as above.

FROM nginx
RUN rm /etc/nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY website-config /etc/nginx

If you want something more bespoke, you could start with a fresh container and install everything you need. This is less divergent from the traditional model, but retains the benefits of containerisation, such as having no side-effects on other services and being easily repeatable. Fedora has an example which takes a base image of their own Linux distribution and uses their package manager, dnf, to install the httpd web server.

FROM fedora:latest
RUN dnf -y update && dnf -y install httpd git && dnf clean all
COPY index.html /var/www/html/index.html

Fedora's example uses the Dockerfile to expose port 80, which we did when we created our container with podman container create. Other instructions can be added to the command line rather than a Dockerfile, as this example from nginx themselves shows. It uses the docker run command [Podman docs] to create and start a container, and has the --mount option to copy /var/www to /usr/share/nginx/html.

$ docker run --name mynginx2 --mount type=bind,source=/var/www,target=/usr/share/nginx/html,readonly --mount type=bind,source=/var/nginx/conf,target=/etc/nginx/conf,readonly -p 80:80 -d nginx