Skip to content

Automating VitePress via GitHub Actions

In this section, we replace the default Nginx page with a VitePress documentation site. We automate the deployment so that pushing to the GitHub repository automatically builds the site and sends it to the VPS.

Goal: When a normal browser visits https://www.curvature.blog, it displays the VitePress site seamlessly served by Nginx on localhost, fully automated via GitHub Actions.


11.1 Update VitePress config for root hosting

Previously, the site was hosted on GitHub Pages, which required a subfolder path. Since we are moving it to the root of our own domain, we must remove that path.

  • Edit config.mts in the repository.
  • Change or remove the base property so it points to the root:
    TypeScript
    export default defineConfig({
    base: '/', 
    title: 'Documents',
    // ...
    })

11.2 Generate a dedicated deployment SSH key

GitHub Actions needs a secure way to log into the VPS without a password. We generate a dedicated SSH key pair for this.

  • Run the keygen command (do not enter a passphrase, just press Enter twice):
    shell
    ssh-keygen -t ed25519 -C "github-actions-deploy" -f deploy_key
  • This creates two files:
    • deploy_key (Private Key - goes to GitHub)
    • deploy_key.pub (Public Key - goes to the VPS)

11.3 Add the Public Key to the VPS (and fix E212 error)

We need to authorize the new key on the VPS for the vpsadmin user.

  • Problem: When trying to edit ~/.ssh/authorized_keys, Vim threw an error: E212: Can't open file for writing.
    • Cause: The vpsadmin user was newly created, so the hidden .ssh directory did not exist yet. Vim cannot create directories automatically.
    • Fix: Exit Vim (:q!), create the directory manually, and lock down the permissions:
      shell
      mkdir -p ~/.ssh
      chmod 700 ~/.ssh
  • Now, open the file:
    shell
    vim ~/.ssh/authorized_keys
  • Paste the contents of deploy_key.pub on a new line, save, and exit.
  • Secure the file permissions:
    shell
    chmod 600 ~/.ssh/authorized_keys
  • Finally, ensure vpsadmin actually owns the Nginx web directory so GitHub can write to it:
    shell
    sudo chown -R vpsadmin:vpsadmin /var/www/html

11.4 Configure GitHub Secrets securely

To let the GitHub Action log in, we must pass it the server details and the private key.

  • Problem: We initially tried pasting all the variables into a single GitHub Secret box.
    • Cause: GitHub Actions requires explicit, separate variables to read them correctly in the YAML file.
    • Fix: In the GitHub repository (Settings > Secrets and variables > Actions), create four distinct secrets:
      • SERVER_IP: YOUR_VPS_IP
      • SERVER_PORT: 3145
      • SERVER_USER: vpsadmin
      • DEPLOY_KEY: (The entire contents of deploy_key, including the BEGIN and END lines)

11.5 The GitHub Actions Workflow

We replaced the GitHub Pages deployment step with an rsync SSH deployment step.

  • Create or update .github/workflows/deploy.yml:
    YAML
    name: Deploy VitePress to VPS
    
    on:
      push:
        branches: [main]
      workflow_dispatch:
    
    jobs:
      build-and-deploy:
        runs-on: ubuntu-latest
        timeout-minutes: 15
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Setup Node
            uses: actions/setup-node@v4
            with:
              node-version: 20
              cache: npm
              cache-dependency-path: VitePress/package-lock.json
    
          - name: Install dependencies
            working-directory: VitePress
            run: npm ci
    
          - name: Build site
            working-directory: VitePress
            run: npm run docs:build
    
          - name: Deploy to VPS via rsync
            uses: easingthemes/ssh-deploy@main
            env:
              SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
              REMOTE_HOST: ${{ secrets.SERVER_IP }}
              REMOTE_USER: ${{ secrets.SERVER_USER }}
              REMOTE_PORT: ${{ secrets.SERVER_PORT }}
              SOURCE: "VitePress/docs/.vitepress/dist/"
              TARGET: "/var/www/html/"

(Important: The trailing slash on the SOURCE path ensures we copy the files inside the folder, not the folder itself).


11.6 Fix Nginx displaying the wrong page

After a successful GitHub Action run, the website still showed the default Nginx welcome page.

  • Problem: The default page was overriding the VitePress index.html.
    • Cause: Debian's default Nginx installation places a "squatter" file named index.nginx-debian.html in the root directory.
    • Fix: Rename or remove the squatter file so Nginx is forced to load the new VitePress index:
      shell
      sudo mv /var/www/html/index.nginx-debian.html /var/www/html/index.nginx-debian.html.bak

11.7 Fix Nginx 404 errors for Clean URLs

When clicking internal links (like /vps_build/01_users), Nginx returned a 404 Not Found error.

  • Cause: VitePress generates clean URLs but actually builds .html files. Nginx was looking for a directory named 01_users instead of 01_users.html.
  • Fix: Update the Nginx configuration to silently try .html extensions.
    • Edit the config:
      shell
      sudo vim /etc/nginx/nginx.conf
    • In the server block listening on 127.0.0.1:8443, update the location / directive to include $uri.html:
      location / {
      try_files $uri $uri/ $uri.html =404;
      }
    • Test and reload:
      shell
      sudo nginx -t
      sudo systemctl reload nginx

11.8 Quick checklist for this stage

After finishing this section, you should have:

  • A VitePress config.mts set to the root base /.
  • A dedicated deploy_key stored securely in GitHub Secrets.
  • vpsadmin owning the /var/www/html directory.
  • A .github/workflows/deploy.yml that successfully uses rsync to push the site on every commit.
  • An Nginx configuration that properly handles .html clean URLs.