How to Deploy a VuePress Site to Your Own Server (Complete Guide for 2026)
Most guides for deploying a VuePress 2 site assume you've already picked a managed static host. The official deployment docs walk through GitHub Pages, GitLab Pages, Firebase, Heroku, Netlify, Vercel, and a handful of others, and they cover those well. This guide assumes the opposite: you've decided you're not using one of them. Maybe the documentation sits behind a VPN, maybe compliance dictates which region the bytes live in, maybe the site is served at a sub-path under a marketing domain, or maybe you're already paying for a VPS and don't want one more vendor in the stack. Whatever the reason, you need a workflow that builds your VuePress site somewhere clean and ships the resulting docs/.vuepress/dist directory to a server you control.
That's the gap DeployHQ fills. It connects to your GitHub, GitLab, or Bitbucket repository, runs your build commands inside a managed build environment, and transfers only the compiled output to your target — a Linux VPS over SSH, a shared host over SFTP, or an object store like Amazon S3, Azure Blob, or Rackspace Cloud Files. By the end of this guide, you'll have a VuePress 2 site that rebuilds on every push to a tracked branch, ships only the static output to your server, can be reverted to any previous deploy with a single click, and supports separate staging and production environments mapped to different branches — all without giving up control over where the files actually live.
Why deploy VuePress with DeployHQ in 2026
VuePress is the Vue-powered static site generator the Vue.js team uses to ship its own documentation. The current 2.x line is the active release; the older 1.x branch lives in maintenance mode under vuejs/vuepress and is mainly kept around for projects that haven't migrated. This guide targets VuePress 2; if you're still on v1, most of the conceptual steps below apply, but the exact commands and paths will differ — finish the v1 → v2 migration first if you can.
The most common reason teams reach for VuePress is documentation. Product docs, internal engineering guides, OSS project handbooks, RFC archives, design-system reference sites — anything where the content is mostly markdown, the structure is a sidebar tree, and the people writing it would rather not learn a CMS. VuePress sits comfortably in that niche because it ships a sensible default theme, supports MDX-style enhancements through Vue components, and emits a fully static site at build time. There's no Node runtime on the deployed server. Whatever host you pick only needs to serve files.
Once you accept that the output is "a static directory full of HTML, CSS, and JavaScript," the deployment problem reduces to two questions: where does the build run, and where do the files land? Managed static hosts answer both with a single product: they pull your repo, build it in their environment, and serve the result from their CDN. That's a fine answer when it fits, and the official VuePress deployment docs cover the popular managed hosts in detail. This guide is for the other case — where you want to control the second half.
The reasons to control the second half haven't changed much over the last few years, but they've gotten more concrete:
- Internal docs. A documentation site that needs to live behind a VPN, an SSO gateway, or an IP allow-list is usually easier to put on the same infrastructure that already enforces those controls than to retrofit access control onto a managed host.
- Data residency and compliance. Security teams typically approve specific VPS providers or cloud regions; shipping there avoids another procurement cycle and another vendor security review.
- Sub-path hosting. When the docs live at
/docs/under your main marketing domain, putting them in a directory of the existing web server is easier than threading them through a managed host's proxy rules. - Existing infrastructure. A VPS you've already provisioned, monitored, and backed up has spare capacity. Adding a documentation site to it is close to free.
- Treating deploys as infrastructure. When the deploy target is yours, the web server config, the TLS termination, and the cache headers all live in the same repos as the rest of your infrastructure.
DeployHQ slots between those two worlds. You get a managed build environment that runs pnpm install && pnpm docs:build (or the npm or yarn equivalent) on every push, without having to wire up GitHub Actions, GitLab CI, or a self-hosted Jenkins. You also get a deployment layer that transfers only the static output to whatever target you've picked — SSH, SFTP, FTP, S3, Azure Blob, or Rackspace Cloud Files. The build half feels like a managed static host; the deploy half puts you back in control of the files.
There's a fair question hovering around this comparison: why not just write a GitHub Actions workflow that runs pnpm docs:build and then rsyncs over SSH? You can, and for one-off projects it's perfectly reasonable. The trade-off is wiring. Once you want a separate staging environment, per-environment secrets, atomic releases, and one-click rollback, the workflow grows. You end up maintaining the deploy pipeline as a small codebase of its own — secrets in GitHub Actions, rsync flags in a shell script, a release-symlink strategy on the server, an artifact cache for node_modules. None of that is hard; it's just work that doesn't have anything to do with shipping documentation. DeployHQ covers most of that scaffolding out of the box. Use whichever option fits your team — this guide just makes the DeployHQ path quick to wire up.
The shape of the work to get a VuePress site live is also worth being honest about. With a managed static host, the steps after creating the repository are: connect the host, set the build command, set the output directory, and you're done. With BYO-server-and-CI, the steps after creating the repository are: stand up a server, configure a web server, mint SSH keys, write a CI workflow, write a rollback strategy, and possibly write your own atomic-release mechanism. DeployHQ sits between those two: the deploy target is yours (the server you've already stood up), but the build, the transfer, the atomic release, and the rollback are handled. The work you have to do is about the same as a managed static host's setup wizard — connect the repo, set the build command, set the output directory, set the deployment path — plus the SSH key on your server. That's it.
There's a second kind of cost to think about: cost of being wrong. Documentation sites have a property that production web apps usually don't, which is that a broken docs deploy spreads through inbound links. A Stack Overflow answer that links to your guide, a partner's integration tutorial that embeds your URL, your own README that points readers to a particular section — those references are durable, and a stretch of broken pages on a documentation site shows up in support tickets long after the deploy that caused it. The atomic-release-plus-one-click-rollback combo is built for that failure mode specifically. Managed static hosts have their own answers, but on BYO-server it would be your job to build the equivalent.
The pipeline this guide builds toward looks like this:
flowchart LR
A[git push to main] --> B[DeployHQ build pipeline]
B --> C[Run pnpm install + pnpm docs:build]
C --> D[Stage docs/.vuepress/dist]
D --> E[Atomic release directory on server]
E --> F[Symlink switch to current]
F --> G[Live VuePress site]
Every commit to the tracked branch becomes a deploy. The build runs in a clean environment. The transfer is restricted to the compiled output. The release lands atomically. And if it breaks, you roll back from the dashboard in a few seconds.
Prerequisites
There are four things to have in place before starting.
A VuePress 2 project in a Git repository. It doesn't matter whether the repo lives on GitHub, GitLab, or Bitbucket; DeployHQ handles all three. If the repository is private, OAuth covers the authentication — you don't need to mint a deploy key by hand. If you haven't scaffolded the project yet, follow the standard VuePress 2 getting-started flow and confirm the project builds locally before continuing.
A local build that works. Run pnpm docs:build (or npm run docs:build, or yarn docs:build) on your machine and confirm it produces docs/.vuepress/dist with index.html inside. If the local build fails, the build will fail on DeployHQ too — fixing it locally first is cheaper than debugging through a remote build log. The standard VuePress 2 scaffold's package.json defines these scripts:
{
"scripts": {
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
}
}
If you've customized the script names or moved the source out of docs/, swap the commands and paths accordingly throughout the rest of the guide.
Node.js v20.9.0 or higher. This is the minimum the VuePress 2 docs currently call out. Match it locally and configure the same version in DeployHQ's build environment so the local and remote builds behave the same way. Pin it in your repository with an .nvmrc file containing the version number, or with an engines field in package.json — either is enough to communicate the requirement to the next person who touches the project.
A deploy target. This is wherever the static output will end up. In practice that means one of:
- A Linux VPS or dedicated server with SSH access — Ubuntu, Debian, Alma, Rocky, anything with
sshdand a web server you've already configured. - A shared host that exposes SFTP or FTP — Plesk, cPanel, DirectAdmin-style providers.
- An object store — Amazon S3, Azure Blob Storage, or Rackspace Cloud Files.
VuePress 2 emits a fully static site, so any target capable of serving static files works. You don't need a Node runtime on the destination; you just need somewhere to put HTML, CSS, JavaScript, and the asset hash bundle that VuePress produces.
One footnote on package managers. VuePress 2's getting-started page documents pnpm, yarn, and npm. pnpm users need to install vue as a peer dependency. Yarn 2+ users need nodeLinker: 'node-modules' in .yarnrc.yml. If you're using some other manager and it works locally, it'll probably work in a build pipeline too — but this guide sticks to the three the upstream docs cover.
Sign up for DeployHQ and create a project
Open the DeployHQ sign-up page and create an account. The free plan covers a small site with one server, which is enough to wire everything up and confirm the pipeline works end to end before deciding whether you need a paid plan.
A project in DeployHQ wraps one Git repository together with its build configuration and one or more deploy targets. You'll create a project per VuePress site, not per environment — staging and production servers attach to the same project as separate targets, both pulling from the same repository on different branches.
When you create the project, DeployHQ asks for two things: the Git host (GitHub, GitLab, or Bitbucket) and the repository name. Authentication goes through OAuth, which means you authorize DeployHQ against your Git provider once and then private repositories are accessible without extra token wrangling. If you'd rather not grant OAuth access at the org level, the alternative is to add DeployHQ's deploy key to the repository as a read-only key; the project setup screen walks through both options.
A few smaller setup decisions worth making at this stage:
- Project name. Use the same string as your site's domain —
docs.example.com— rather than a generic name like "docs." Future you will appreciate it when the project list has half a dozen entries. - Time zone. DeployHQ timestamps deploy entries in the time zone you set on the project. Match it to your team's working hours; it makes reading the deploy history less confusing.
- Notifications. Default email notifications go to the account owner on every deploy. If you'd rather route deploy notifications into Slack, Microsoft Teams, or a custom webhook, the integrations section on the project covers that. You can leave it alone until later — none of this is on the critical path for getting the first deploy working.
Once the project exists and DeployHQ has cloned your repo into its build infrastructure, the dashboard shows an empty deploy history and a project configuration tree on the left. You're ready to wire up the build pipeline.
Connect your VuePress repository
If you connected your repository during project creation, DeployHQ already knows where the source lives. There are still two pieces of configuration worth checking before moving on.
Branch mapping. DeployHQ doesn't deploy from a repository in the abstract; it deploys from a specific branch to a specific server. The branch selection happens at the server level, not at the project level. For now, just confirm DeployHQ has detected the branches in your repo correctly — you'll pick main (or master, or whichever branch is your production source of truth) when you add a server later.
Default config detection. DeployHQ inspects package.json to suggest a sensible default build command. For VuePress 2 projects with the standard scaffold, the suggestion typically lines up with pnpm docs:build-style commands. The suggestion is helpful but not binding — you'll override the exact command in the next section anyway.
If you're working with a private repository on GitHub, GitLab, or Bitbucket and want to keep a tight scope on the OAuth grant, the project-level integration page exposes the option to switch from OAuth to a single-repo deploy key after the fact. The deploy key is read-only by design; DeployHQ never needs write access to your source.
It's also worth a short detour to confirm where DeployHQ does and doesn't reach into your repository. The full clone happens inside DeployHQ's build environment on every deploy. It does not happen on your destination server. Nothing about your source code is pushed to production — only the contents of the deployment subdirectory you configure, which for a VuePress build is docs/.vuepress/dist. This separation is what makes the build pipeline safe even on shared hosts where you'd rather not expose the Git tree.
Configure the build pipeline
The build pipeline is the longest section in this guide because it's where most of the decisions live. There are three things to get right: the install/build command pair, the Node version, and the build environment variables.
Install and build commands
The build pipeline runs a sequence of shell commands inside DeployHQ's build environment, then transfers files. For a standard VuePress 2 project using pnpm, the pipeline is two commands joined with &&:
pnpm install --frozen-lockfile && pnpm docs:build
pnpm install --frozen-lockfile installs strictly from pnpm-lock.yaml, refuses to update the lockfile, and fails fast if package.json and the lockfile disagree. That's exactly what you want in a build pipeline — non-deterministic installs in CI are a frequent source of "works locally, fails in production" surprises. pnpm docs:build then runs the docs:build script defined in package.json, which in the standard scaffold expands to vuepress build docs.
If you use a different package manager, swap the install and build pair:
- npm:
npm ci && npm run docs:build - yarn:
yarn install --frozen-lockfile && yarn docs:build
npm ci is npm's equivalent of pnpm install --frozen-lockfile — it installs strictly from package-lock.json and fails fast on a mismatch. yarn install --frozen-lockfile is the yarn 1.x flag; yarn 2+ users should keep the same flag, which still works for honoring yarn.lock. The build script convention (docs:build) is the same across all three; that's a VuePress scaffold convention, not a package-manager-specific name.
Three smaller variants worth knowing about:
- If your VuePress project uses a non-default source directory — say the markdown lives in
documentation/instead ofdocs/— the script inpackage.jsonprobably already saysvuepress build documentation, but the convention is to keep the script name asdocs:buildregardless. Run whatever script your project actually defines. - If you're using a monorepo where the VuePress site is a workspace package, the install step usually runs at the repo root and the build step targets the workspace:
pnpm install --frozen-lockfile && pnpm --filter ./packages/docs docs:build. The deployment subdirectory you set in the next section will then be the workspace'sdocs/.vuepress/dist, not the repo root's. - If you've added pre-build steps — generating API reference markdown from JSDoc, for example — chain them in:
pnpm install --frozen-lockfile && pnpm generate:api-docs && pnpm docs:build. The build pipeline is just a shell command; it'll run whatever you put in there.
Pin the Node version
VuePress 2 requires Node.js v20.9.0 or higher. DeployHQ's build environment lets you select a Node version per project from its build-environment configuration. Pick a 20-line release (or newer LTS) and stick with it — drifting between Node versions across builds occasionally surfaces transitive dependency surprises that are painful to diagnose later.
Pin the version in two places so the requirement is explicit:
- In the repository. An
.nvmrcfile at the project root with a single line containing the version (v20.18.0, for example). Anyone usingnvmlocally will pick up the right version automatically. If you'd rather useenginesinpackage.json, do that — it's the same signal, expressed differently. - In DeployHQ. Select the matching major version in the build-environment config. DeployHQ won't read your
.nvmrcto choose a version; you have to set it explicitly on the project.
If you ever bump the local Node version (because a transitive dependency requires a newer one, for instance), bump the DeployHQ project's Node version too. The two should stay in sync.
Environment variables
A documentation site doesn't always need build-time secrets, but most non-trivial ones end up needing at least a couple — analytics IDs, search-provider API keys (Algolia DocSearch is the common case), or the public site URL. VuePress reads these from process.env at build time the same way any Node process does.
DeployHQ exposes a config variables section per server and per project. Variables you set there are injected into the build environment as standard environment variables. They're encrypted at rest, can be scoped to all servers or to specific ones, and can be locked to prevent accidental display in the UI.
A note on syntax that catches people out the first time. Inside DeployHQ's own command strings and config files (not your Node code), custom variables are referenced with the %VARIABLE_NAME% syntax. So if you wanted to pass the variable into a build command literally, you'd write BASE_URL=%BASE_URL% pnpm docs:build. But inside your VuePress build itself, the variable is available as process.env.BASE_URL like any other environment variable — no special syntax. The percent-sign form is for DeployHQ's templating; the standard process.env access works once the variable is in the environment.
A few VuePress-specific patterns worth knowing about:
- Public site URL. Many VuePress configs read the deployed URL from an environment variable so the same source builds correctly for staging and production. In
.vuepress/config.ts, you might have something likehead: [['link', { rel: 'canonical', href: process.env.SITE_URL }]]. SetSITE_URLper server. - Analytics ID. If you're using the official
@vuepress/plugin-google-analyticsor similar, the property ID can come in asprocess.env.GA_IDand be applied conditionally so dev builds don't fire analytics. - Search credentials. Algolia DocSearch requires
appId,apiKey, andindexName. Keep them out of source control and in DeployHQ's config-variables block, scoped to the relevant environment.
There's no reason to wire up secret management separately for this; DeployHQ's config variables are the right place for everything the build needs at build time.
base and sub-path deploys
The base config option in VuePress 2 — set inside .vuepress/config.ts or .vuepress/config.js — is one of the most consequential settings in this whole guide, because getting it wrong produces a site that loads but breaks all its assets, and the failure mode looks confusing if you don't know to check.
The default value of base is /. That means VuePress builds absolute asset URLs starting from the site root. If you serve the site at https://docs.example.com/ with the deployment path mapped to the webroot, the default works without changes.
If you serve the site at a sub-path — https://example.com/docs/, say, because your main marketing site lives at example.com and the documentation lives in a sub-directory — you need to tell VuePress about it:
// .vuepress/config.ts
import { defineUserConfig } from 'vuepress'
export default defineUserConfig({
base: '/docs/',
// ...
})
Both the leading and trailing slashes are required. /docs/ is correct; docs/, docs, and /docs are all wrong, and each produces a different flavor of broken asset URL. VuePress emits asset links relative to whatever you set as base, so a base mismatch shows up at runtime as /assets/styles.css (or whatever) returning a 404 because the asset is actually served at /docs/assets/styles.css.
If you have one VuePress project that's deployed to both a root domain and a sub-path — for example, a staging environment at docs-staging.example.com (root) and a production deploy at example.com/docs/ (sub-path) — drive base from an environment variable so the same source builds correctly for both:
// .vuepress/config.ts
export default defineUserConfig({
base: (process.env.VUEPRESS_BASE ?? '/') as `/${string}/`,
// ...
})
Then set VUEPRESS_BASE=/docs/ on the production server's config variables and leave it unset on staging. The build picks up the right value from process.env either way.
This is the foot-gun the upstream VuePress deployment guide doesn't help you with, because the managed hosts it covers each handle sub-path serving differently. With BYO-server, the base value is yours to choose once and freeze.
Build caching and faster repeat builds
Out of the box, DeployHQ runs each build in a clean environment. That's the safest default — there's no risk of stale state from a previous build leaking in — but it's also why the first install can feel slow if your dependency tree is large. The build pipeline configuration lets you opt into caching specific directories between builds. The pragmatic choice for a VuePress project is to cache the package manager's store: ~/.local/share/pnpm/store/v3 for pnpm, ~/.npm for npm, or ~/.yarn/cache for yarn. The lockfile-enforced install commands above will re-use the cache when packages haven't changed and rebuild it when they have.
Don't cache node_modules itself. Cache the package manager's store and let the install command produce a fresh node_modules from it. Caching node_modules directly tends to confuse package managers when versions change and produces subtly broken installs.
Set the build output directory and configure your server
This is the single most important step in the entire guide. Skipping it is the most common cause of "why did my entire repository end up on my server" support tickets, and it's also the step the upstream VuePress docs don't need to cover, because the managed hosts each have their own opinion about it baked in.
Set the deployment subdirectory
DeployHQ's default behavior, on a newly-created server within a project, is to transfer the entire repository root to the destination. For a typical web app deploy, that makes sense — the deployed files live at the root of the repo. For a VuePress build, it's wrong, because the repository root contains source markdown, the VuePress config files, the package manager's lockfile, your CI workflows, your entire node_modules tree, and the rest of the source tree — not the compiled site.
Tell DeployHQ to only transfer the compiled output by setting the deployment subdirectory to:
docs/.vuepress/dist
That's the default output directory for VuePress 2's vuepress build docs command. The full default is ${sourceDir}/.vuepress/dist, which resolves to docs/.vuepress/dist for the standard scaffold. If you've overridden the dest option in .vuepress/config.ts — for example, dest: 'dist/docs' — point DeployHQ at the path you've configured, not the default.
The deployment subdirectory is a per-server setting. The same project might deploy to a production server (subdirectory docs/.vuepress/dist) and a staging server (also docs/.vuepress/dist); set it explicitly on each server you add, rather than relying on the project root.
Deploy to a Linux VPS over SSH
Add a server to the project and pick the SSH protocol. DeployHQ generates a deploy key for the server pairing; paste its public half into ~/.ssh/authorized_keys on the destination machine under whichever user account will own the deployed files. Common choices: a dedicated deploy user, the www-data user, or whatever user owns your webroot. Whichever you pick, make sure it has write access to the deployment path.
Configure the protocol settings:
- Hostname or IP. The reachable address of the server from DeployHQ's build environment.
- Port. Default
22for SSH; override if you've moved the service. - Username. The system user the SSH key belongs to.
- Deployment path. The directory the web server serves from. Common shapes:
/var/www/docs.example.com/current/,/home/deploy/sites/docs/current/, or/srv/www/docs/. Pick something explicit; a path the web server actually points at is better than a guess.
For atomic deploys (the recommended pattern), make the deployment path the current symlink target — DeployHQ will write each release into a sibling directory and switch the symlink atomically once the transfer is complete. The atomic flow is covered in detail later in this guide.
The web server itself stays your responsibility. For a VuePress site served from a root domain, a minimal Nginx server block looks like this:
server {
listen 443 ssl http2;
server_name docs.example.com;
root /var/www/docs.example.com/current;
index index.html;
location / {
try_files $uri $uri/index.html $uri.html =404;
}
}
The try_files directive is the part that makes both /getting-started/ and /getting-started resolve to the right file without surprising the reader with a 404. VuePress 2 emits each page as a directory containing index.html by default, which is what makes the $uri/index.html fallback work.
If you're serving the site at a sub-path under an existing domain, the location block changes shape:
location /docs/ {
alias /var/www/example.com/docs/current/;
try_files $uri $uri/index.html $uri.html =404;
}
Use alias, not root. root would append /docs/ to the path again and produce a doubled directory. Pair the alias with a base: '/docs/' setting in .vuepress/config.ts as covered earlier — the two have to agree, or the asset URLs won't line up with where the files actually sit.
For Apache, the equivalent is a DocumentRoot (or Alias for sub-paths) plus an .htaccess block that handles the clean URLs:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.html [L]
Caddy is the simplest of the three because its file server already does the right thing by default:
docs.example.com {
root * /var/www/docs.example.com/current
file_server
try_files {path} {path}/index.html {path}.html
}
Whichever server you're running, the principle is the same: serve index.html for unmatched routes and let VuePress's emitted directory structure handle the rest.
Deploy to shared hosting over SFTP or FTP
The flow on a shared host is identical to SSH, except the protocol is SFTP (preferred, since it's encrypted) or FTP. DeployHQ asks for the hostname, username, password (or SSH key for SFTP), and remote path. Set the deployment subdirectory to docs/.vuepress/dist as above; set the remote path to whatever directory cPanel, DirectAdmin, or Plesk serves from — typically public_html/, public_html/docs/, or httpdocs/.
Shared hosts generally don't give you a custom Nginx or Apache config. For a default VuePress build that's usually fine, because the standard scaffold emits pages as directories with index.html and most shared hosts resolve index.html automatically. If the site loads at the home page but /getting-started/ returns a 404, drop an .htaccess file into the deployed directory with the rewrite rules above; most shared hosts honor .htaccess even when the rest of the Apache config is off-limits.
On a shared host, atomic releases are usually not available because the host doesn't expose a writable symlink mechanism — DeployHQ falls back to overwriting files in place. The trade-off is that a deploy is no longer instantaneous; readers may briefly see a half-deployed state if their request lands during a transfer. For a documentation site this is rarely a problem, but it's worth knowing about.
Deploy to S3, Azure Blob, or Rackspace Cloud Files
Object storage is a good fit when you want to put a CDN in front of the site — CloudFront in front of S3, Azure Front Door in front of Blob Storage, or Akamai in front of Rackspace Cloud Files. DeployHQ writes files directly into the bucket; the CDN handles delivery.
Three things to keep in mind for object-storage targets. First, the bucket needs static-website-hosting enabled (S3) or static-content serving configured (Azure, Rackspace) before deploys produce a working site — pointing a browser at a bucket without that enabled will show a JSON error from the storage API, not the VuePress site. Second, the deployment subdirectory is still docs/.vuepress/dist. Third, cache-control headers are your responsibility. VuePress 2 fingerprints asset filenames in /assets/, which means those URLs can carry long-lived cache headers safely; index.html files should carry shorter cache headers because their content changes between builds.
A practical cache pattern for an S3 bucket fronted by CloudFront: set Cache-Control: public, max-age=31536000, immutable on /assets/* (one year, immutable), and Cache-Control: public, max-age=300 on top-level HTML and index.html (five minutes). Pair that with a CloudFront invalidation on each deploy if you need cache busts to land within seconds rather than minutes.
If your deploy target is S3 and you're serving the site through CloudFront, make sure the Origin Access Identity (or Origin Access Control) on the distribution allows the bucket to be read from CloudFront but not directly from the public internet. The bucket policy and CloudFront distribution settings here are out of scope for this guide; AWS's own docs are the right reference.
File permissions
For SSH targets, the user the deploy key belongs to needs write access to the deployment path. The simplest configuration is to give a dedicated deploy user ownership of the deployment directory and run the web server as a different user that has read access. A typical setup:
sudo adduser --disabled-password deploy
sudo mkdir -p /var/www/docs.example.com
sudo chown -R deploy:www-data /var/www/docs.example.com
sudo chmod -R 750 /var/www/docs.example.com
The deploy user writes files; the www-data group reads them; everything else can't see them. Adjust the group and modes to match your distribution's conventions if they differ.
Deploy and verify
With the build pipeline and the server both configured, you're ready to run the first deploy.
Trigger it manually from the project dashboard. DeployHQ clones the repository into its build environment, runs the install and build commands, and transfers the contents of docs/.vuepress/dist to the deployment path on the server. The first deploy is a full transfer — every file in the build output ships to the destination. Subsequent deploys send only the files that changed. DeployHQ tracks the previous deploy state and computes a diff before transferring, which is why incremental deploys after the first one usually feel fast.
While the deploy runs, the dashboard streams the build log. The log is the first place to look when something's wrong — it shows the exact commands DeployHQ executed, their stdout and stderr, and the file diff that landed on the server. A clean deploy ends with a green check; a failed deploy ends with the failing command and its error output.
Once the deploy finishes, verify three things:
Load the home page in a browser. If it renders the VuePress home, the build succeeded and the transfer landed in the right place. If you see a directory listing, the default Nginx welcome page, or a 403, the web server's root (or alias, for sub-path deploys) doesn't match the deployment path. Fix the web server config and reload it; the deploy itself is fine.
Load a deep route. Visit https://docs.example.com/getting-started/ (or whatever a real second-level URL on your site is) and confirm it resolves to the right page. If you see a 404, the web server's try_files directive isn't catching the clean URL. Run curl -I https://docs.example.com/getting-started/ to see whether the response is coming from Nginx (Server: nginx) or from VuePress's own 404 page; the answer tells you which layer to fix.
Open the deploy log. Confirm the transferred file count matches your expectation and that no warnings about ignored files are flagging assets you actually wanted to ship. The log is also where you'd notice if a recent change to a .deployignore file accidentally excluded /assets/ from the transfer.
Once the manual deploy works end-to-end, switch on automatic deploys. The setting is per server, on the project dashboard. With it enabled, every push to the mapped branch fires the same pipeline — build, transfer, atomic release — without manual intervention. This is the automatic deployment from Git flow the docs describe in more detail; in practice, once it's on, you stop thinking about deploys and just push.
If you've made it this far, you've shipped one build. The rest of this guide covers the patterns that turn a one-off deploy into a durable pipeline — separate environments, atomic releases, rollback, and the foot-guns that catch new VuePress projects. If that sounds useful for your own VuePress site, sign up for a free DeployHQ account and wire up the build pipeline against your real repo. The free plan is enough to get through everything below.
Environments, branches, and previews
A typical VuePress documentation setup runs production at docs.example.com and a staging site at docs-staging.example.com so content reviewers can see proposed changes before they merge into the production branch. Some teams also keep a "next" environment for the upcoming major version of the docs, served at docs-next.example.com or docs.example.com/next/. DeployHQ models all three the same way: each one is a server attached to the same project, mapped to a different branch.
Production and staging
Attach two servers to the project. One is the production server (deployment path /var/www/docs.example.com/current/); the other is staging (/var/www/docs-staging.example.com/current/). Map production to main and staging to a staging branch. From that point on, pushing to main deploys to production and pushing to staging deploys to staging, with no extra ceremony.
Each server has its own config-variables block. That's how you give each environment a different VuePress configuration without forking the source. Set SITE_URL=https://docs.example.com and VUEPRESS_BASE=/ on the production server; set SITE_URL=https://docs-staging.example.com and VUEPRESS_BASE=/ on staging. Reference them in .vuepress/config.ts:
export default defineUserConfig({
base: (process.env.VUEPRESS_BASE ?? '/') as `/${string}/`,
head: [
['link', { rel: 'canonical', href: process.env.SITE_URL ?? 'https://docs.example.com' }],
],
})
The same pattern applies to other build-time settings — analytics property IDs, Algolia DocSearch indexes, feature flags. Each server's config vars are scoped to that server, so secrets don't leak across environments.
Per-branch deploys for content review
VuePress is documentation-heavy, which means most pull requests are content changes. The standard team workflow is: a writer opens a PR against staging, a reviewer checks it on the staging site, the PR merges to staging, and the change is picked up on staging via DeployHQ's automatic deploys. Once a batch of staged changes is ready for release, staging is merged into main and the same flow ships them to production.
That setup doesn't need anything special — it's just the two-server pattern above with a branch-promotion workflow on top. The friction it removes is that no one is asking the reviewer to read raw markdown or guess what the rendered output will look like. They click the staging URL and see the real thing.
A "next" environment for the upcoming docs
If you publish docs for multiple versions of your product and you keep the upcoming version's docs in a next branch, attach a third server: next.example.com/docs/ (or docs-next.example.com), mapped to the next branch. The same atomic release pattern applies; the only difference is that the next deploy doesn't get the same SEO-canonical handling — you'll typically want a <meta name="robots" content="noindex"> on the next site so it doesn't compete with the live docs in search. Inject that conditionally from a config variable.
Per-PR preview deploys
A more advanced setup gives each open pull request its own preview URL. The shape varies by team. Some run a third "preview" server that's pointed at whichever branch a content reviewer is currently looking at, switching it as needed. Others run a small script that triggers a one-off DeployHQ deployment via API when a PR is opened, lands the files in a path keyed by PR number, and tears it down on PR close. There's no single right answer; for documentation sites in particular, a single staging environment is usually enough, and per-PR previews are more of a developer-tools convenience than a content-review one.
Promoting builds between environments
DeployHQ runs a fresh build on every deploy, which means staging and production builds aren't byte-for-byte identical even if the source is — small things like timestamps in the rendered output can vary. For most documentation sites this doesn't matter; the staging environment exists to validate that the content renders correctly, not to validate the exact bytes that will ship. If you do need byte-identical promotion, the pattern is to make staging and production builds deterministic (no timestamps in output, no randomness in plugin behavior) and run the same commit through both pipelines. In practice, VuePress builds are already deterministic enough that this is rarely an issue.
Documenting the deployment topology
One operational habit worth keeping: write down which branch deploys where, and put the answer in the repository — not in a wiki page somebody made three jobs ago. A short DEPLOYMENT.md at the project root that lists the environments, their URLs, their branches, and where the configuration lives means new team members can answer the "which branch is production?" question without having to log into the DeployHQ dashboard. Three lines is enough:
- production: deployed from `main` to https://docs.example.com (DeployHQ project: docs-prod)
- staging: deployed from `staging` to https://docs-staging.example.com (DeployHQ project: docs-stg)
- next: deployed from `next` to https://docs-next.example.com (DeployHQ project: docs-next)
If the deploy topology lives in source, it survives team churn and tool changes the same way the rest of the codebase does.
Operations and observability
A static site is roughly as observable as the web server in front of it, which is good news — you get the same access logs and metrics you'd get for any other site on the same server. There are a few VuePress-specific things worth setting up so the answer to "did the last deploy break anything?" is something you can read at a glance.
Deploy notifications
DeployHQ can post deploy events to Slack, Microsoft Teams, generic webhooks, and email. For a documentation site the useful default is: post successful deploys to a low-priority channel that records the deploy log link and the commit summary, and post failed deploys to a higher-priority channel that pings whoever is on docs duty. The signal-to-noise ratio matters here — if every successful deploy pings the team, the team learns to ignore the channel, which means failed deploys get ignored too. Send most deploys to where they can be reviewed asynchronously and reserve the loud channel for failures.
Deploy log retention
The deploy log shows every command DeployHQ ran, every file transferred, and every byte of stdout and stderr. It's the right place to look when "the site changed but I'm not sure what" comes up. The dashboard keeps the log for every deploy in the project's history. If you need a longer audit trail (some teams want every documentation change traced to a deploy event for compliance), the log can be fetched via DeployHQ's API and archived externally. The common pattern is to write the log to a long-term store (S3, a SIEM ingestion endpoint, a Loki bucket) on a successful or failed deploy via a post-deploy webhook.
Site monitoring
Documentation sites tend to be lower-priority in synthetic monitoring than transactional services, which is reasonable, but they still benefit from a basic health check. A single uptime check that loads the home page and a sample deep route is enough to catch the failure modes that would otherwise show up as a support ticket. The deep route check is what catches try_files misconfigurations after a deploy; the home-page check is what catches everything else. Wire the health check into your existing monitoring stack — Uptime Kuma, Healthchecks.io, Pingdom, whatever you already use — and route the alert to the same channel as the DeployHQ deploy failures.
Lighthouse and content-quality checks
VuePress sites are usually fast by default — the static output is small, the asset graph is shallow, and the build emits sensibly hashed asset filenames. That said, a lightweight Lighthouse run on the production URL after each deploy catches performance regressions before readers do. Some teams run Lighthouse as a post-deploy step inside DeployHQ's build pipeline; others run it externally via a cron job. The output is most useful when it's compared against the previous deploy's score, so trend tracking is more valuable than a one-shot number.
Atomic releases, zero-downtime, and one-click rollback
Atomic releases are the feature that makes BYO-server deploys feel as reliable as a managed static host. For a VuePress documentation site, they matter for a specific reason: VuePress fingerprints asset filenames between builds. Build A produces app.a1b2c3.js; build B produces app.d4e5f6.js. If a reader's browser loads index.html from build A and then requests app.d4e5f6.js mid-transfer because their HTML resolved during a deploy in progress, they get a 404 and a broken page.
What atomic means here
Atomic releases sidestep that race entirely. Instead of overwriting files in place at the deployment path, DeployHQ writes the new release into a separate timestamped directory — say /var/www/docs.example.com/releases/20260607T101542Z/ — and only after the transfer is complete switches the current symlink to point at the new directory. The web server is configured to serve from current, so it sees either the entire old release or the entire new release, never a mix. The switch itself is a single symlink update, which is effectively instantaneous from the kernel's perspective; that's what makes the deployment a zero downtime deployment on supported targets. Readers never see a half-deployed state.
The directory layout on the server looks something like this:
/var/www/docs.example.com/
├── current -> releases/20260607T101542Z/
└── releases/
├── 20260607T101542Z/
│ ├── index.html
│ ├── assets/
│ │ ├── app.d4e5f6.js
│ │ └── styles.7a8b9c.css
│ └── ...
├── 20260607T093017Z/
│ ├── index.html
│ ├── assets/
│ │ ├── app.a1b2c3.js
│ │ └── styles.234567.css
│ └── ...
└── 20260606T184429Z/
└── ...
DeployHQ retains a configurable number of previous releases on the server so rollback can flip the symlink back without re-transferring the files. The retention count is a per-server setting; the default is usually sensible, but if your release sizes are large enough to matter, lower it.
The atomic flow is what powers atomic releases across the project — same pattern, same dual-directory layout, same symlink switch. For VuePress sites specifically, it solves the asset-fingerprint mismatch problem cleanly: a request can't span two releases because the symlink the web server resolves is the same for the duration of a request.
Why this matters even for docs
There's a temptation to assume zero-downtime deploys are an overkill feature for documentation. Docs aren't critical infrastructure; a 404 on a deploy doesn't take down a business. The counter-argument is that docs are exactly where you'd notice the breakage, because docs URLs end up in inbound links — Stack Overflow answers, blog posts, partner integrations, your own GitHub issues. A broken asset on a deploy doesn't just affect the reader who happens to load the page at the wrong second; if that reader posts about it on the same platform, the broken URL spreads. The cost of atomic deploys is essentially zero (DeployHQ does it by default on SSH targets), so the question is more "why wouldn't you?" than "is this worth setting up?"
One-click rollback
Things break. A new VuePress plugin upgrade quietly breaks the sidebar layout. A search-index regeneration runs late and corrupts the results. A new theme override introduces an MDX parsing edge case that turns half the docs into rendered HTML for < and > characters. The repair path on a managed static host is usually: revert the commit, wait for the rebuild, apologize.
On DeployHQ you skip the wait. Every deploy can be reverted to any earlier successful release with one-click rollback from the dashboard. Pick the previous release from the project's history, click rollback, and the deploy target is restored. On atomic SSH targets the rollback is the same atomic symlink switch as a normal deploy — readers either see the broken release or the previous one, never a half-rolled-back mix. On non-atomic targets like FTP, the rollback re-transfers the previous release's files in place.
The thing rollback won't fix, and that's worth being explicit about: rollback restores file state on the deploy target. It doesn't undo DNS changes, TLS certificate renewals, web-server config edits, or anything outside the files DeployHQ deployed. If your breakage is in nginx.conf rather than in the VuePress build output, rolling back the deploy won't help; you have to revert the server config separately. The same applies to third-party services — an Algolia DocSearch index that was reindexed against the broken build needs its own rollback, and a CDN cache that has the broken HTML cached at the edge needs a cache invalidation.
For the common breakage class — a bad commit produced a bad build that produced a broken site — rollback is the fastest path back to working, measured in the time it takes to click the button.
Cache invalidation on object-storage targets
If your VuePress site is deployed to S3 fronted by CloudFront (or Azure Blob fronted by Front Door), there's one extra wrinkle on rollback: the CDN edge may have the previous response cached. The fingerprinted asset URLs are safe — they're immutable by design — but index.html typically isn't, and rolling back to a previous release means the new (well, restored old) index.html references different fingerprinted assets than what the CDN edge has cached.
The fix is to invalidate the HTML paths on the CDN at the same time as the rollback. CloudFront's invalidation API accepts /index.html and /*.html patterns; running an invalidation as a post-deploy step on the rollback (or scripting it via your CDN provider's API) closes the gap.
For SSH-direct deploys without a CDN, there's no equivalent concern — the symlink switch fully resolves on the next request.
Rolling back the rollback
Rollback is itself just another release, which means rollbacks have their own entries in the deploy history. If you roll back to release N-2 and then realize release N was actually fine and the breakage was somewhere else, you can roll forward to release N the same way — pick the release from the history and click the action again. There's no special "undo rollback" flow because there doesn't need to be one; every release is just a release, and the symlink can point at any of them.
The implication for documentation teams is that the failure mode "I rolled back too far" isn't catastrophic. You can move the symlink forward again as soon as you've identified the right release. The only release that's genuinely gone is one DeployHQ has pruned from its retention window — but pruning is configurable, and erring on the side of keeping more releases on disk is cheap as long as your VuePress build output isn't enormous.
What rollback looks like for the team
A useful pattern for documentation rollbacks: keep a short note in your team's incident channel each time you roll back, with the deploy ID and the symptom. Three lines is enough — "rolled back deploy abc123 to xyz789, symptom: sidebar component crashed on /api/auth/, root cause: unmerged change to client.js". The note is what makes the rollback show up in postmortems and the next planning meeting, instead of disappearing into the dashboard's deploy history. None of this is DeployHQ-specific, but it's the operational habit that makes one-click rollback actually useful in practice rather than just impressive in a feature page.
That's the atomic, zero-downtime, rollback-ready pipeline. DeployHQ runs the build, ships an atomic release, and gives you one-click rollback out of the box on supported targets. If you'd like to wire this up against your VuePress site rather than reading about it, start a free project on DeployHQ and connect your repo. The setup is the same flow you've been reading through; running it on your real repo takes a few minutes.
Troubleshooting
These are the failure modes that account for most of the support tickets we've seen on VuePress deploys. They group into build failures, deploy failures, and post-deploy site failures.
Build fails: lockfile mismatch
The build pipeline runs pnpm install --frozen-lockfile (or npm ci, or yarn install --frozen-lockfile), and the install step fails with a message about the lockfile being out of sync with package.json. This happens when someone added or upgraded a dependency locally without committing the updated lockfile, or when a tool other than the project's standard package manager wrote a parallel lockfile.
Fix it by running the matching install command locally — pnpm install for pnpm, npm install for npm, yarn install for yarn — and committing the resulting lockfile. The strict-install flags will pass on the next deploy. Don't be tempted to relax the flag (pnpm install instead of pnpm install --frozen-lockfile) in the pipeline; that hides the problem rather than fixing it, and leaves the door open for non-deterministic builds.
A related variant: the project root has both package-lock.json and pnpm-lock.yaml, because someone ran npm install once on a pnpm project (or vice versa). Pick one package manager for the project, delete the other lockfile, and add the unused one to .gitignore so it doesn't come back.
Build fails: Node version mismatch
The install or build step fails with an error about an unsupported Node version, often surfacing through a transitive dependency. The fix is to set the Node version explicitly on DeployHQ's build-environment configuration and to match it locally via .nvmrc. VuePress 2 requires Node.js v20.9.0 or higher; pick a version in that range or a more recent LTS.
A subtle variant: the build runs, but a plugin warns about deprecated Node APIs and the resulting site is missing some content. Bump the Node version up rather than ignoring the warning — VuePress's plugin ecosystem moves quickly enough that "warnings only" today often turns into "hard errors" within a release or two.
Build succeeds, deployment is empty
The build pipeline runs cleanly. The deploy "succeeds." But the server has nothing on it, or has the repository source instead of the rendered site. This is the deployment-subdirectory bug, and it's the single most common one.
The deployment subdirectory wasn't set, or was set to the wrong path. Open the project's server configuration, set the deployment subdirectory to docs/.vuepress/dist, and redeploy. The next deploy will transfer the compiled output.
If you've customized VuePress's dest config in .vuepress/config.ts, the deployment subdirectory needs to match — dest: 'dist/docs' in the VuePress config means the deployment subdirectory should be dist/docs, not docs/.vuepress/dist. Whatever path the build actually writes into is what DeployHQ needs to transfer from.
Site loads, but assets 404
The home page renders. CSS doesn't load; client-side navigation breaks; the browser's network tab shows a wall of 404s on /assets/... URLs. This is almost always a base configuration mismatch.
Check .vuepress/config.ts. If you serve the site at https://example.com/docs/, base needs to be '/docs/' — with both the leading and trailing slashes. 'docs/', 'docs', '/docs', and an unset base all break differently. Once you fix the value, rebuild and redeploy. If you're driving base from an environment variable, confirm the variable is set on the right server in DeployHQ's config-variables block, not just on staging.
A second variant of the same problem: the site is served at a root domain (no sub-path), but a reverse proxy or CDN is mangling the path. Curl the asset URL directly against the origin (curl -I https://docs.example.com/assets/app.a1b2c3.js) to see whether the origin is serving the file. If the origin is fine but the CDN isn't, the bug is in the CDN config — not in VuePress.
SSH key not authorized
The deploy fails on the transfer step with an SSH authentication error. The DeployHQ deploy key isn't in ~/.ssh/authorized_keys on the destination, or it's there but under the wrong user, or the file permissions on ~/.ssh are too permissive and sshd is refusing to read it.
Fix order: confirm the public key listed under the DeployHQ server configuration is present in ~/.ssh/authorized_keys for the right user; confirm ~/.ssh is mode 700 and authorized_keys is mode 600; confirm the user has a valid shell (some hardened distros disable login on service users by default). Running ssh -i <local-key> deploy@server from a machine you control with the same key reproduces what DeployHQ is doing and usually surfaces the precise failure mode.
Permission errors on the target directory
The transfer fails on a Permission denied error when DeployHQ tries to write to the deployment path. The user the deploy key belongs to doesn't have write access to the path.
Either chown the deployment path to the deploy user, add the user to the group that owns the path, or pick a different deployment path the user can write to. For the standard deploy user pattern from earlier in this guide:
sudo chown -R deploy:www-data /var/www/docs.example.com
sudo chmod -R 750 /var/www/docs.example.com
The deploy user owns the tree; www-data reads it for the web server; anyone else can't see it.
Slow builds
The first deploy on a fresh DeployHQ project tends to feel slow because the install step is rebuilding the package manager's store from scratch. Subsequent builds get faster once caching is configured. If they're not getting faster, check that you've enabled caching on the build environment and that you're caching the package manager's store rather than node_modules directly. Cache ~/.local/share/pnpm/store/v3 for pnpm, ~/.npm for npm, ~/.yarn/cache for yarn.
A separate cause of slow builds is plugin-heavy projects — a VuePress site with a large API reference generator that runs at build time, or with hundreds of generated pages from a CMS source. There's not much DeployHQ can do about plugin runtime; the fix is in the project itself. Profile the build locally (time pnpm docs:build) to see whether the slow step is install or build, and target the right half.
VuePress 1 project
If the build is failing with errors that don't look like VuePress 2's output, the project might still be on VuePress 1. The two are different enough at the API and CLI level that this guide doesn't cover v1; the migration to v2 is documented in VuePress's own migration guide.
For a v1 project the build command is vuepress build docs (often defined as docs:build in package.json too, but the underlying CLI differs), and the output directory is docs/.vuepress/dist — the same path, coincidentally. The configuration file is .vuepress/config.js only (no TypeScript), and the config API differs significantly from v2. If you're still on v1, the practical advice is to migrate before investing in deployment work; the v1 to v2 migration is mostly mechanical for documentation sites, and shipping the rest of this guide against the active VuePress release saves you re-doing it later.
Search index missing or stale
VuePress documentation sites commonly use either the built-in search plugin or Algolia DocSearch. Both emit their indexes into the build output at build time. If search is broken after a deploy, run pnpm docs:build locally and confirm the search index files are present in docs/.vuepress/dist. If they're present locally but missing on the server, check the DeployHQ deploy log; a .deployignore pattern (or an over-aggressive excluded files rule) can silently skip them.
For Algolia DocSearch specifically, the index is hosted externally and updated by an Algolia crawler. A deploy that produces correctly-rendered pages but stale search results usually means the crawler hasn't re-indexed since the deploy. Trigger a manual reindex from the Algolia dashboard; the issue isn't on the deploy side.
Build fails: missing peer dependency
The build pipeline fails during install with a warning about an unmet peer dependency, typically pointing at vue. This shows up most often on pnpm projects because pnpm is strict about peer dependencies by default. The VuePress 2 docs explicitly call out that pnpm users need to install vue as a peer dependency.
The fix is to add vue to the project's dependencies explicitly:
pnpm add vue
Commit the updated package.json and pnpm-lock.yaml and redeploy. If you'd rather not pin the exact vue version because the rest of the dependency tree is already resolving it, you can also opt into pnpm's auto-install-peers behavior in .npmrc:
auto-install-peers=true
Either works; the explicit dependency is more transparent for future readers of the project.
Build fails: missing nodeLinker (yarn 2+)
If you're using yarn 2 or later and the build fails immediately with errors about modules not being found, the cause is usually the default yarn Plug'n'Play resolver, which VuePress's build doesn't always handle cleanly. The VuePress 2 docs recommend switching to the classic node_modules layout. Add this to .yarnrc.yml:
nodeLinker: node-modules
Commit the file, run yarn install locally to regenerate the lockfile, and the next deploy should pick up the change. This is a one-time fix per project.
Deploy succeeds, but the browser still shows the old site
You deployed, the deploy log shows the new files landed, but Ctrl+F5 still shows the previous version. This is a CDN or browser cache, not a deploy failure.
For a CDN-fronted deploy (CloudFront in front of S3, Azure Front Door in front of Blob), invalidate the HTML paths on the CDN as a post-deploy step. For an SSH deploy with no CDN, hard-reload the browser and clear local cache; the symlink switch is instantaneous at the origin, so anything stale at the browser layer is purely client-side.
If you're using a CDN like CloudFront and want cache invalidation to happen automatically on every deploy, wire an invalidation API call as a post-deployment shell command on the project. DeployHQ can run arbitrary shell commands after the transfer completes; an aws cloudfront create-invalidation call (with credentials supplied as config variables) is a few lines.
Conclusion and next steps
You've set up a pipeline that connects your VuePress 2 repository to a server you control, runs the build inside a managed environment, ships the compiled output atomically to the destination, and gives you one-click rollback when something breaks. That's the whole thing.
The configuration that produces this result is small. A project per VuePress site. One build pipeline: pnpm install --frozen-lockfile && pnpm docs:build. A deployment subdirectory of docs/.vuepress/dist. A server per environment, mapped to a branch. A web server pointed at the deployment path. Add config variables for whatever build-time secrets your site uses, and the pipeline runs itself.
The pieces of the deploy that don't fit into a managed static host's model — sub-path serving, IP-restricted documentation, data-residency requirements, sites that share infrastructure with the rest of your stack — are where this setup pays off, because the deploy target is yours and the build is still managed.
If you've followed along reading rather than building, the next step is to wire it up against a real repository. Sign up for DeployHQ, connect your VuePress repo, and run the first deploy. The free plan is enough to validate the pipeline end-to-end before deciding whether to upgrade.
For related deployment guides, see the Docusaurus walkthrough at deploying Docusaurus to your own server — the build pipeline differs but the architecture is similar — or the Aider guide on a terminal-based AI pair programmer if you're pairing AI-assisted documentation editing with the deploy flow above. The broader guides index lists the rest of the framework-specific walkthroughs and integrations across the DeployHQ documentation hub.
Questions? Email support@deployhq.com or reach us at @deployhq.