How to deploy & self-host VuePress on DigitalOcean

How to deploy & self-host VuePress on DigitalOcean

We may earn an affiliate commission through purchases made from our guides and tutorials.

Here’s how to deploy and self-host a VuePress site on DigitalOcean without fuss. We’ll start from a blank Droplet, build the site, and serve it efficiently with Nginx. I’ll also add a quick production checklist at the end so you don’t cut corners.

You’ll need a DigitalOcean account to spin up a Droplet—obvious, but easy to forget when you’re in a hurry.

VuePress is a Vue-powered static site generator. You write in Markdown, VuePress generates pre-rendered HTML for each page, and once loaded in the browser it behaves like a SPA. In production, you serve plain static files. That means: no app server to baby-sit, minimal moving parts, and excellent performance.

What we’re building

  • A small Ubuntu Droplet on DigitalOcean.
  • Nginx serving your VuePress build (.vuepress/dist) directly.
  • Optional: Let’s Encrypt TLS.
  • Git-based deploys (local build + rsync) or on-server build—your call.

Prerequisites

  • A domain you control (point it to the Droplet later).
  • A fresh Ubuntu 22.04 or 24.04 Droplet.
  • SSH access as a sudo user.

If you’ve never installed Nginx on Ubuntu before, DigitalOcean’s docs are solid and current. I’ll fold the critical bits in below.

Create the Droplet and SSH in

Pick Ubuntu 22.04 or 24.04, give it a sensible hostname, and add your SSH key. From your machine:

ssh root@your_droplet_ip
adduser deploy
usermod -aG sudo deploy
rsync -avz ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh

Reconnect as your non-root user:

ssh deploy@your_droplet_ip

Optional but wise—basic firewall:

sudo apt update
sudo apt install ufw -y
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status

Install Nginx

sudo apt update
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl status nginx

You should see the default Nginx welcome page at http://your_droplet_ip/.

Create an A record for @ (and optionally www) to your Droplet’s public IP. Once it propagates, you can swap server_name to your domain.

Bootstrap your VuePress project (locally or on the server)

VuePress expects a source dir with a .vuepress folder inside; the default build output is YOUR_SOURCE/.vuepress/dist. You can build locally and upload the dist directory, or build on the server. Either way, the toolchain is the same.

Option A — Build locally (recommended)

On your laptop/workstation:

# Create a new project directory
mkdir docs-site && cd docs-site

# Initialize package.json
npm init -y

# Install VuePress (v2) locally
npm install -D vuepress

# Scaffold basic docs
mkdir -p docs/.vuepress
printf '# Hello VuePress\n\nThis is my site.' > docs/README.md

# Add scripts
npm pkg set scripts.docs:dev="vuepress dev docs"
npm pkg set scripts.docs:build="vuepress build docs"

# Run dev server locally (optional)
npm run docs:dev

Build:

npm run docs:build

Your static site is now in docs/.vuepress/dist. The CLI and default output behavior are documented here.

Option B — Build on the Droplet

If you prefer to build on the VPS, install Node LTS (via apt or NodeSource), clone your repo, and run the same scripts:

# On the server
sudo apt update
sudo apt install -y curl ca-certificates gnupg

# Install Node.js LTS quickly with NodeSource (example for 20.x)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs git

# Clone your project and build
git clone https://github.com/your/repo.git /var/www/docs-site
cd /var/www/docs-site
npm ci
npm run docs:build

VuePress’s own docs confirm the build command and output directory; we’re just doing it on the server.

Deploy the static files to the server

If you built locally, ship the dist folder to the VPS. I like an rsync push to a versioned release directory.

# On your local machine:
rsync -avz --delete docs/.vuepress/dist/ deploy@your_domain_or_ip:/var/www/docs-site/current/

Create the target path first:

# On the server
sudo mkdir -p /var/www/docs-site/current
sudo chown -R deploy:www-data /var/www/docs-site
sudo find /var/www/docs-site -type d -exec chmod 755 {} \;
sudo find /var/www/docs-site -type f -exec chmod 644 {} \;

Configure an Nginx server block for the site

Create a dedicated server block, enable it, and disable the default.

sudo nano /etc/nginx/sites-available/vuepress.conf

Paste this minimal, correct config:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/docs-site/current;
    index index.html;

    # Serve static files
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Security & performance niceties
    location ~* \.(?:jpg|jpeg|gif|png|svg|webp|ico|css|js|woff2?)$ {
        access_log off;
        add_header Cache-Control "public, max-age=31536000, immutable";
        try_files $uri =404;
    }
}

Enable it:

sudo ln -s /etc/nginx/sites-available/vuepress.conf /etc/nginx/sites-enabled/vuepress.conf
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Why Nginx over a Node static server (like pm2 serve)? Nginx is purpose-built to serve static files fast, handle TLS, and scale with minimal memory. PM2’s static server is convenient for quick previews, but Nginx is the boring, reliable choice for production.

Add HTTPS (Let’s Encrypt)

If your DNS is pointed correctly, Certbot can fetch and install a certificate in minutes:

sudo apt update
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Follow the prompts; Certbot will update the server block to redirect HTTP→HTTPS and install auto-renewal. DigitalOcean’s step-by-step Nginx guides link out to certbot usage if you want screenshots and options.

CI/CD (optional but worth it)

You have two clean deployment paths:

  • A. Local build + rsync (simple): Keep building locally and push dist with rsync. It’s fast and predictable.
  • B. GitHub Actions (hands-off): On push to main, build and rsync the dist folder to the Droplet over SSH.

Example GitHub Actions workflow:

name: Deploy VuePress
on:
  push:
    branches: [ "main" ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install & Build
        run: |
          npm ci
          npm run docs:build

      - name: Deploy via rsync
        uses: burnett01/rsync-deployments@6.0
        with:
          switches: -avzr --delete
          path: docs/.vuepress/dist/
          remote_path: /var/www/docs-site/current/
          remote_host: ${{ secrets.DROPLET_HOST }}
          remote_user: deploy
          remote_key: ${{ secrets.DEPLOY_KEY }}

VuePress’s deployment docs mostly focus on third-party platforms, but the key assumption is always the same: ship .vuepress/dist to where your web server can serve it. We’re doing exactly that with Nginx.

Common gotchas (and quick fixes)

  • Blank page or 404s on nested routes: Your try_files rule must fallback to /index.html to support SPA-style routing. See the Nginx block above. Serving the dist directory directly is the correct approach for VuePress.
  • Wrong output path: By default, VuePress puts the build at YOUR_SOURCE/.vuepress/dist. Don’t point Nginx at the source directory; point it at the built dist.
  • Slow first byte / missing compression: Enable gzip (or brotli if you’re comfortable) in Nginx, and set long-cache headers for hashed assets (we did).
  • Tempted to run pm2 serve forever: It works, but you’re reinventing a web server. Use Nginx in front; it’s simpler and sturdier. PM2’s own docs call it a convenience tool.

Production checklist (don’t skip this)

[ ] DNS points to Droplet; both root and www work
[ ] Nginx serves /var/www/docs-site/current correctly
[ ] HTTP→HTTPS redirect in place (certbot did it)
[ ] Cache headers set for static assets
[ ] 404 page exists in dist (optional but nice)
[ ] Automated deploy (CI or a simple script) documented in README
[ ] Regular OS patching (unattended-upgrades) enabled

Quick “all-in-one” commands you’ll use often

Rebuild locally and deploy:

npm run docs:build
rsync -avz --delete docs/.vuepress/dist/ deploy@example.com:/var/www/docs-site/current/

Swap releases safely (optional):

# On the server
mkdir -p /var/www/docs-site/releases
cp -a /var/www/docs-site/current /var/www/docs-site/releases/$(date +%F-%H%M%S)
rsync -avz --delete ~/upload/dist/ /var/www/docs-site/current/
sudo systemctl reload nginx

Renew certificates (cron handles this, but manual is fine):

sudo certbot renew --dry-run

Recap

You built your VuePress docs, shipped the static dist to /var/www/docs-site/current, and let Nginx do the serving. That’s the right, boring architecture for a static site: fast, cacheable, cheap to run, and easy to automate. VuePress’s defaults (and docs) align cleanly with Nginx on a plain Ubuntu Droplet, which is exactly what we want for long-term maintainability.

Was this helpful?

Thanks for your feedback!
Alex is the resident editor and oversees all of the guides published. His past work and experience include Colorlib, Stack Diary, Hostvix, and working with a number of editorial publications. He has been wrangling code and publishing his findings about it since the early 2000s.

Leave a comment

Your email address will not be published. Required fields are marked *