Skip to main content

Deploy Docusaurus on Nomad

· 3 min read
Stephan Hochdörfer

In this blog post, I've covered how our CI setup looks like to test, build and deploy Docusaurus on our Nomad cluster. However, the blog post was missing the Nomad configuration, which we'll cover in this blog post in detail.

Since Docusaurus is a static site generator, all we need to let it run in production is a "simple" web server. We decided to go with nginx, and thus, we use nginx:stable-bullseye as a base image.

The Dockerfile looks like this:

FROM node:16.14.0-alpine AS docusaurus
COPY ./blog /app
WORKDIR /app
RUN yarn
RUN yarn build

FROM nginx:stable-bullseye
WORKDIR /var/www
RUN apt-get update && apt-get upgrade -y && apt-get install -y gosu
COPY --from=docusaurus /app/build/. /usr/share/nginx/html
RUN touch /var/run/nginx.pid
COPY ./docker/start.sh /var/www/start.sh
COPY ./docker/nginx.conf /etc/nginx/nginx.conf
RUN ["chmod", "+x", "/var/www/start.sh"]
ENTRYPOINT [ "/var/www/start.sh" ]
EXPOSE 8080

The nginx configuration file is responsible primarily for handling invalid url parameters. Other than that, no additional "magic" is involved. The start.sh script makes sure that nginx is run as a user:

#!/bin/bash
chown -R nginx:nginx /var/www /var/log/nginx /var/cache/nginx /var/run/nginx.pid
chmod ug+rwx /var/cache/nginx /var/run /var/log/nginx
chmod 622 /dev/stderr && chmod 622 /dev/stdout
echo "Ready to serve requests.."
gosu nginx:nginx nginx

Since the built Docker container includes all the files needed, we do not need to mount any host volumes. That means the Nomad job for the blog can run on any of our Nomad clients, which is a big plus. Given that, this is what the Nomad job configuration looks like:

job "docusaurus" {
datacenters = ["dc1"]
type = "service"

update {
max_parallel = 2
min_healthy_time = "10s"
healthy_deadline = "3m"
progress_deadline = "10m"
auto_revert = true
canary = 0
}

migrate {
max_parallel = 2
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
}

group "blog" {
count = 1

restart {
attempts = 3
interval = "30m"
delay = "15s"
mode = "fail"
}

task "frontend" {
driver = "docker"

config {
image = "example.com/bitexpert/blog:${TAG}"
port_map {
http = 8080
}
force_pull = true
}

resources {
cpu = 20
memory = 64 # 256MB
network {
mbits = 1
port "http" {}
}
}

service {
name = "docusaurus-prod"
tags = [
"traefik.enable=true",
"traefik.http.routers.blog-prod.entrypoints=websecure",
"traefik.http.routers.blog-prod.rule=Host(`blog.bitexpert.de`)",
]
port = "http"

check {
name = "alive"
type = "tcp"
interval = "10s"
timeout = "2s"
}
}
}
}
}

The job docusaurus contains one group blog, which includes one task frontend, responsible for running the built docker image containing our nginx + Docusaurus setup.

The ${TAG} variable is set in our GitLab CI pipeline and references the Git tag used to trigger the build. This ensures that Nomad uses the image built by GitLab CI to run the container.

The container exposes port 8080, which is mapped to a custom port by Nomad and picked up by Traefik to route the incoming web requests to the running docker container.

We haven't yet configured green/blue deployments with Nomad to minimize downtime. That means, currently, there's a little bit of downtime after the deployment until Traefik detects the newly deployed container.

I'll cover the new setup in another blog post once it's done.