Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

MystMD Website Deployment Instruction


Owner: Vadim Rudakov, lefthand67@gmail.com
Version: 0.4.0
Birth: 2025-12-17
Modified: 2026-01-09


This guide outlines how to set up an automated documentation pipeline using

  • MyST Markdown,

  • GitHub Actions,

  • Podman, and

  • Traefik.

Files to work with

The list of files used for configuration:

  1. Repository:

    1. Git

      • .gitignore

      • .github/workflows/deploy.yml

      • SSH keys

    2. MyST: myst.yml

  2. Server:

    1. Nginx: nginx.conf

    2. Podman: play_nginx.yaml

    3. Traefik: /home/user/.local/share/containers/storage/volumes/traefik-data/_data/dynamic/myst-website.yml

1. Local Repo Configuration

1.1 Initialize MyST Locally

Before the automation can work, your repository needs to be recognized as a MyST project.

  1. Open your terminal in the root of your local repository.

  2. Run myst init. Follow the prompts if there are any.

  3. Crucial: Open your .gitignore file. If myst.yml was added there, remove it. You must track myst.yml in Git, while keeping the _build/ folder ignored.

  4. Commit and push the myst.yml to your repo.

1.2 Edit ‘myst.yml’

This file is an entry point for rendering your repo to html. Here you can:

  • set the project’s and site’s title,

  • set the link to the github project,

  • set logo and favicon,

  • exclude some repo’s paths for rendering.

As you can see, we exclude all .md files from rendering because we use jupytext ipynb-md pairing.

2. Github Side Preparation

2.1 Configure GitHub Secrets

To allow GitHub to deploy files to your server, you must store your credentials securely.

  1. In your GitHub Repo, go to Settings > Secrets and variables > Actions.

  2. Add the following Repository secrets:

    • SERVER_IP: Your server’s public IP or domain.

    • SERVER_USER: The SSH username (e.g., root or a deploy user).

    • SERVER_PORT: Your custom SSH port (e.g., 2222).

    • SSH_PRIVATE_KEY: The private key whose public counterpart is in the server’s authorized_keys.

2.2 Enable the GitHub Action

Your workflow file (.github/workflows/deploy.yml) automates the “Build and Sync” process.

  1. Push your changes to the main branch.

  2. Navigate to the Actions tab on GitHub to monitor the progress.

  3. The workflow will:

    • Install mystmd.

    • Build the Markdown into static HTML.

    • Use rsync over the custom SSH port to move the HTML into /home/server-user/website/html.

3. Server Side Configuration

3.1 Prepare the Server Environment

Your server needs rsync installed to receive the files, and the directory structure must match your manifests.

  1. Install rsync:

    • Run sudo apt install rsync (or equivalent for your OS).

  2. Create Directories:

    mkdir -p /home/server-user/website/html
  3. Set Permissions: Ensure your SSH user owns the directory:

    ls -l /home/server-user/website

    Set correct permissions if needed using:

    chown -R server-user:server-user /home/server-user/website
  4. Place Config:

    • Move your nginx.conf to /home/server-user/website/nginx.conf.

3.2 Deploy the Podman Pod as a Systemd Service

Instead of running the pod manually, we will use Podman’s native integration with systemd. This ensures your website starts automatically after a reboot and is managed as a background service.

See the K8S YAML manifest example for the given website here: play_nginx.yaml

Generate the Systemd Escape Path

Systemd requires a specifically formatted “escaped” path to reference the Kubernetes manifest file. Generate this by running:

systemd-escape /home/server-user/website/play_nginx.yaml

Copy the output of this command (e.g., home-server-user-website-play-nginx.yaml).

Configure the Environment Variable

To make service management easier, we will store the service name in your .bashrc.

  1. Open your bash configuration:

    vi ~/.bashrc
  2. Add the following line at the end of the file, replacing <escaped_path> with the result from the previous step:

    export MYST_WEBSITE_SERVICE="podman-kube@<escaped_path>.service"
  3. Apply the changes:

    source ~/.bashrc

Enable and Start the Service

Now, you can manage your MyST website using standard systemd commands. This will launch the Nginx container and serve the files synchronized by GitHub.

# Reload systemd to recognize the changes
systemctl --user daemon-reload

# Enable the service to start on boot
systemctl --user enable $MYST_WEBSITE_SERVICE

# Start the service immediately
systemctl --user start $MYST_WEBSITE_SERVICE

Verify the Deployment

Check the status of your service to ensure the Nginx container is running correctly:

systemctl --user status $MYST_WEBSITE_SERVICE

3.3 Configure Traefik Routing

Traefik acts as the entry point, handling SSL/TLS and routing traffic from your domain to the Podman container.

  1. Add the Router and Service to your Traefik dynamic configuration.

    # example of the directory with Traefik dynamic files
    /home/user/.local/share/containers/storage/volumes/traefik-data/_data/dynamic/website_traefik.yml
  2. Ensure the loadBalancer URL points to the hostPort defined in your pod manifest (e.g., http://<your-website>:8080).

Troubleshooting Checklist

  • 404 Error: Ensure Nginx is listening on port 80 inside the container and the Pod manifest maps containerPort: 80 to your hostPort.

  • Permission Denied: Check that the GitHub SSH user has write access to the target folder on the server.

  • No Site Config: Ensure myst.yml is present in the root of your GitHub repository.

  • Traefik Issues: Check the Traefik dashboard to ensure the service is “Healthy” and the URL matches the host’s listener.

4. Local Testing

$ uv tool install mystmd
$ uv run myst start

uv tool install ensures that the installed mystmd is not the project dependency but the global tool.

You do not need to initialize the myst project because you are testing the existing project - the repo’s myst.yml.

That’s it: now you have a locally running website of the repo with all the files in your working directory, i.e. all the local files you have in the directory, including unstaged and in .gitignore. Here you can test all the changes you have made to the website or your notebooks.

When testing is done, you can safely remove _build directory with the rendered files.

You can also remove mystmd, optionally:

$ uv tool uninstall mystmd

Appendix A: Configuration Files

See attached files for nginx.conf, Traefik YAML, and the Kubernetes/Podman manifest in helpers/website/configs/.

ls configs
mutli-site/  nginx.conf  play_nginx.yaml  website_traefik.yml

Other files are active repo files, so you should inspect them directly in the repo.

Appendix B: Multi-Site Configuration (Single Pod)

This section describes how to host multiple MyST websites within a single Nginx instance and Podman Pod by using port-based routing. This approach is highly efficient as it shares a single container process across multiple repositories.

Each website’s files can be maintained in different git repos, you will need to configure github actions for each one respectively, but all of these sites share the same config files on the server.

# server
$ tree --dirsfirst -L 2 ~/website/
/home/user/website/
├── site_a
│   └── html
├── site_b
│   └── html
├── nginx.conf
└── play_nginx.yaml

5 directories, 2 files

B.1 nginx.conf

To serve multiple repositories, the Nginx configuration is updated with independent server blocks, each listening on a unique internal port. Each block points to a specific directory (root) where the respective site’s HTML is mounted.

B.2 play_nginx.yaml

The Pod manifest is updated to define multiple hostPath volumes—one for each git repository’s build output—and to expose unique hostPort values for external access.

When using this specific multi-site configuration in a single Pod, you must pay attention to how the

  • Host Paths,

  • Port Mappings, and

  • Volume Mounts

align with your nginx.conf.

Any mismatch between these three areas will result in “404 Not Found” or “Bad Gateway” errors.

  1. Host Path Accuracy

    The hostPath must point to the exact location on your server where the MyST HTML files are stored.

    • Permissions: Ensure the user running the container has read permissions for these directories (e.g., /home/user/website/site-a/html).

    • Directory Type: The type: Directory ensures that the pod will only start if these folders already exist on your host machine.

  2. Port Mapping and Naming

    This config uses port-based routing rather than domain-based routing.

    • Unique Names: Each port defined in the ports section must have a unique name (e.g., port-site-a and port-site-b).

    • External Access: You will access your sites using the hostPort values. In this example, Website A is at http://<server-ip>:8080 and Website B is at http://<server-ip>:8081.

    • Internal Alignment: Your nginx.conf must contain two server blocks: one listen 80; and one listen 81; to match the containerPort values defined here.

  3. Volume Mount Alignment

    The mountPath inside the container is what Nginx “sees.”

    • Root Directory: In your nginx.conf, the root directive for Site A must be set to /usr/share/nginx/site_a, and Site B must be /usr/share/nginx/site_b.

    • Config Overwrite: The nginx-config volume mounts to /etc/nginx/conf.d/default.conf. This completely replaces the default Nginx welcome page configuration with your custom multi-site rules.

B.3 Traefik Dynamic File

Remember to create an additional dynamic file for routing the new website through your Traefik instance. You can copy the existing file for the first site described in 3.3 Configure Traefik Routing, rename it, and change the:

  • hostname

  • port number

and all the occurences of the first website naming in router, services, etc. naming. No additional information for this file needed.

B.4 Deployment Summary

Restart the service:

systemctl --user restart $MYST_WEBSITE_SERVICE

and check your websites. Using this configuration, Nginx routes traffic based on the port number requested by the user:

  • Website A: Accessible at https://first-website.

  • Website B: Accessible at https://second-website.

This setup ensures that only one Nginx container is running on the server, significantly reducing memory and CPU overhead compared to running a separate Pod for every repository.