Building a home web server: Containerizing our App

RMAG news

Welcome back to This Old Box! A series that covers the journey of building and running a web server out of an old 2014 Mac mini.

In this post we’ll walk through how we containerized our first web application, configured our Kubernetes cluster with our new container, and exposed that cluster to the world.

The project we deployed is the most simple from an infrastructure standpoint. It’s a static site that is mainly html, css, and a wee bit of client-side JavaScript. Logistically, the whole site fit into a single container.

Docker

There are several container runtimes in the industry today. We chose to use Docker as it seems to be the industry standard, especially on a Mac. Docker is also well documented, and thus should be the easiest to learn. Although, I am concerned because I’ve heard the runtime is a bit greedy with CPU and memory resources. I hope this Old Box can handle it.

We installed Docker Desktop in our last post. With that application running we built a docker image by writing the instructions in a Dockerfile.

This first website we containerized is a collection of static sites that my son created called AjMcWeb. The instructions for building the container for this site were relatively simple. All we really needed to do was copy all the html, javascript, css, and image files for the site into the container, run them on nginx, and expose port 3001 on the container. The Dockerfile to accomplish these steps looked like this.

FROM nginx
COPY . /usr/share/nginx/html
EXPOSE 3001

Using that Dockerfile, we ran the following docker build command inside the same folder to build a running image of that container.

docker build -t ajmcweb:0.1 .

This command built the Docker image. The -t argument told Docker to tag the image with the the name ajmcweb and gave it a version of 0.1. The trailing . told Docker to use the Dockerfile in the current directory.

Now that we had our site encapsulated inside a Docker container we needed to deploy it to the world. That’s where Kubernetes came in.

Kubernetes

Docker Desktop includes a standalone Kubernetes server and client that we enabled in our last post. This enablement instantiated the images required to run the Kubernetes server as containers, and installed the kubectl utility–used to manage our Kubernetes cluster–on our machine.

ngrok for ingress

Before configuring our Kubernetes cluster, we needed to install the ngrok Ingress Controller so we could use ngrok to provide ingress to our application. Ingress is “an API object that manages external access to the services in a cluster.”

ngrok is an ingress platform that helps provide access to a variety of things including Kubernetes clusters as well as devices and other networks. The reason why we chose ngrok for this particular project is because ngrok works behind a NAT.

Here’s how we set up ngrok and its ingress controller.

Setting up ngrok

First, we signed up for a free ngrok and then upgraded to a paid plan so we could use our own domains for our sites–like www.ajmcweb.com for example.

In order to use ngrok we first needed to get our ngrok authtoken in our ngrok dashboard. Then, we used that to set an NGROK_AUTHTOKEN environment variable on our system.

Next we created an API Key and set that to an NGROK_API_KEY environment variable.

The last environment variable we set was NAMESPACE. This is going to be the Kubernetes namespace we’ll use for our clusters on this box. In our case, we used macbox.

Both of these environment variables will be used when we install the ngrok ingress controller. But, before we leave ngrok, I’ll mention how we set up our own domains inside of our ngrok account.

Setting up custom domains in ngrok

We followed the ngrok guide for setting up Custom Domains which can be boiled down to two steps. First, we went to the Domains section of the ngrok dashboard and clicked the New Domain button.

We typed in www.ajmcweb.com and clicked Continue. This gave us a Domain Record value that we copied and pasted into a CNAME record on our domain registrar.

Setting up ngrok ingress controller

To establish ingress, or provide user access, we wanted to incorporate the ngrok account that we configured previously. ngrok has an ingress controller for Kubernetes which we installed as a helm chart with the following steps:

We added the ngrok repo to our helm settings.

helm repo add ngrok https://ngrok.github.io/kubernetes-ingress-controller

Then, we installed the helm chart with the following command, referencing the environment variables we set earlier.

helm install ngrok-ingress-controller ngrok/kubernetes-ingress-controller
–namespace $NAMESPACE
–create-namespace
–set credentials.apiKey=$NGROK_API_KEY
–set credentials.authtoken=$NGROK_AUTHTOKEN

Configuring Kubernetese

Going back to our code we captured in a Docker container we were ready to define our application deployment in Kubernetes. We began with configuring a Service that is a pod running on http and port 80.

apiVersion: v1
kind: Service
metadata:
name: ajmcweb
namespace: macbox
spec:
ports:
name: http
port: 80
targetPort: 80
selector:
app: ajmcweb

Then we defined a Deployment object to run our ajmcweb container we built earlier. In the code below you’ll see that we’re running version 0.3 of that container, which is denoted as ajmcweb:0.3.


apiVersion: apps/v1
kind: Deployment
metadata:
name: ajmcweb
namespace: macbox
spec:
replicas: 1
selector:
matchLabels:
app: ajmcweb
template:
metadata:
labels:
app: ajmcweb
version: v0.1
spec:
containers:
name: ajmcweb
image: ajmcweb:0.3
ports:
name: http
containerPort: 80

And, finally, we configured our ingress object using the ngrok Kubernetes Operator that we installed earlier.


apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ajmcweb
namespace: macbox
spec:
ingressClassName: ngrok
rules:
host: www.ajmcweb.com
http:
paths:
path: /
pathType: Prefix
backend:
service:
name: ajmcweb
port:
number: 80

Our ingress object had one rule which defined the host as www.ajmcweb.com and the backend would run the ajmcweb service on port 80.

We applied those Kubernetes configurations with a kubectl apply -f <filename>.yaml and www.ajmcweb.com came right up in the browser!

Success!

Conclusion

This felt great to be able to get our simple static website deployed on our server and have ngrok handle the network settings to make the site reachable.

Our next task will be to get a more complex application online. My son has an application he built to help track the time he spends practicing his saxophone. The app uses Node.js and MySQL for the backend and has a much more rich frontend. It will require some more effort both from a deployment standpoint and from a processing perspective. Stay tuned as we figure out how to get this application online and keep it running!