This setup is based on git and post receive hooks for publishing.

Requirements

Quartz 4.5+ requires Node.js >= 22 and npm >= 10.9.2. Your Docker image handles this, but you’ll need a compatible environment if testing locally.

First, create a bare git repo for the notes called content.git:

git init --bare content.git
cd content.git
touch test
git add test
git commit -m "Initial commit"
cd ..

Then, clone the original quartz repo into a bare state. Call it quartz.git:

git clone --bare https://github.com/jackyzha0/quartz.git quartz.git

Now, we need a local clone of that repo which we can later mount in a docker container. Let’s call it quartz-site:

git clone quartz.git quartz-site

The content.git repo should new be added as a submodule to the quartz-site.

cd quartz-site
git rm -r --cached content
rm -rf content
git commit -m "Remove original content directory"
 
# Add content.git as submodule
git config protocol.file.allow always
GIT_ALLOW_PROTOCOL=file git submodule add -f ../content.git content
git commit -m "Add content.git as a sub"
git push

Things that could go wrong

  • Adding submodules using the file protocol is considered unsafe, so be sure you know what you are doing.

Next, create post receive hooks for our content.git repo.

content.git/hooks/post-receive
#!/bin/bash
export PATH=/usr/bin:/bin:/usr/local/bin
set -e
 
echo "[Content hook] Updating Quartz repo submodule and rebuilding"
 
QUARTZ_DIR="/strg/src/secret/quartz-site"
GIT_DIR="$QUARTZ_DIR/.git"
BRANCH="v4"
 
export GIT_WORK_TREE="${QUARTZ_DIR}"
export GIT_DIR="${GIT_DIR}"
 
export GIT_ALLOW_PROTOCOL=file
 
cd "${QUARTZ_DIR}" || exit 1
 
echo "PWD: $(pwd)"
echo "GIT_DIR: $GIT_DIR"
echo "GIT_WORK_TREE: $GIT_WORK_TREE"
/usr/bin/git status
 
# Make sure you're acting *inside* a working repo
if [ ! -d "$GIT_DIR" ]; then
  echo "[$GIT_DIR] is not a valid /usr/bin/git repo"
  exit 1
fi
 
# Update submodule from content.git
/usr/bin/git --git-dir="$GIT_DIR" --work-tree="$QUARTZ_DIR" submodule update --remote --init
 
# Stage submodule change
/usr/bin/git --git-dir="$GIT_DIR" --work-tree="$QUARTZ_DIR" add content
 
# Only commit if needed
if ! /usr/bin/git --git-dir="$GIT_DIR" --work-tree="$QUARTZ_DIR" diff --cached --quiet; then
  /usr/bin/git --git-dir="$GIT_DIR" --work-tree="$QUARTZ_DIR" commit -m "Auto-update content submodule"
  /usr/bin/git --git-dir="$GIT_DIR" --work-tree="$QUARTZ_DIR" push origin "$BRANCH"
 
  docker exec quartz-site npx quartz build
else
  echo "[Content hook] No submodule changes detected"
fi

Don’t forget to make the hook executable:

chmod +x content.git/hooks/post-receive

Now, at the end of the hook is a call to a docker container that does not yet exist. Next we are going to create that!

We’ll use a quite simple docker container. The following are the files used for creating our container:

Consider the following file structure:

quartz-site
|-- compose.yml
|-- Dockerfile
|-- entrypoint.sh
\-- nginx.conf
compose.yml
services:
  quartz-site:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: quartz-site
    ports:
      - "8098:80"
    volumes:
      - /strg/src/secret/quartz-site:/site
    restart: unless-stopped
Dockerfile
FROM node:22-slim
 
# Install nginx
RUN apt-get update && apt-get install -y nginx && apt-get clean
 
# Copy nginx config
COPY nginx.conf /etc/nginx/nginx.conf
 
# Copy entrypoint
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
 
WORKDIR /site
 
CMD ["/entrypoint.sh"]
entrypoint.sh
#!/bin/bash
set -e
 
echo "[Entrypoint] Building Quartz site..."
 
cd /site
 
# Ensure Node version is correct
echo "[Entrypoint] Node version: $(node -v)"
echo "[Entrypoint] NPM version: $(npm -v)"
 
# Install dependencies and initialize quartz
if [ ! -d "node_modules" ]; then
    echo "[Entrypoint] Installing npm dependencies..."
    npm install
    npx quartz create
fi
 
# Run Quartz build
echo "[Entrypoint] Running Quartz build..."
npx quartz build
 
# Start Nginx
echo "[Entrypoint] Starting Nginx..."
exec nginx -g "daemon off;"
nginx.conf
events {}
 
http {
 
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
 
    server {
        listen 80;
        root /site/public;
        index index.html;
        error_page 404 /404.html;
 
        location / {
            try_files $uri $uri.html $uri/ =404;
        }
    }
}

Now, let’s build and start our container:

docker compose up --build -d

On your local machine you can now clone the content.git repo and start creating notes :)

Every time you push to the origin content.git quartz should rebuild your notes.