EmDash is a full-stack TypeScript CMS built on Astro — often described as the spiritual successor to WordPress. It combines a modern admin interface with Astro's rendering performance, Portable Text for structured content, and passkey-first authentication. If you're building a content-driven site with EmDash and need reliable, repeatable deployments from Git, [DeployHQ](https://www.deployhq.com) makes the process straightforward.

In this guide, you'll set up an EmDash project for Node.js deployment, connect it to [DeployHQ](https://www.deployhq.com), configure your build pipeline, and deploy to a VPS or cloud server with zero manual file transfers.

## What Is EmDash?

EmDash is an open-source CMS that runs as an [Astro](https://astro.build/) integration. Unlike traditional CMSs that bolt a content API onto a separate frontend, EmDash ships everything in one project — the admin panel, content editor, authentication, media management, and your frontend templates.

A few things that set EmDash apart:

- **Astro-native** : Your site is an Astro project. EmDash adds the CMS layer as an integration, so you keep full control over routing, components, and rendering.
- **Portable Text editor** : Content is stored as structured JSON (via TipTap), not raw HTML. This makes content reusable across different presentation formats.
- **Passkey-first auth** : No passwords by default. Users authenticate with WebAuthn passkeys, with OAuth and magic links as fallbacks.
- **Schema-in-database** : Content collections are defined in the database, not in code files. This means editors can create new collections without touching the codebase.
- **Plugin system** : Extensible via a `definePlugin()` API with sandboxed execution on Cloudflare or in-process on Node.js.

EmDash supports multiple deployment targets — Cloudflare Workers, Docker containers, or plain Node.js servers. This guide focuses on Node.js deployment via [DeployHQ](https://www.deployhq.com), which covers VPS hosting, cloud instances, and dedicated servers.

## Prerequisites

Before starting, make sure you have:

- **Node.js 22.12.0 or later** installed on both your local machine and your deployment server
- **A Git repository** (GitHub, GitLab, or Bitbucket) containing your EmDash project
- **A [DeployHQ](https://www.deployhq.com) account** — you can [sign up for a free trial](https://www.deployhq.com/signup) if you don't have one
- **SSH access to your server** with Node.js 22+ installed
- **A process manager** like PM2 or systemd configured on your server

If you haven't created an EmDash project yet, scaffold one with:

```
npm create emdash@latest my-site
cd my-site
npm install
```

This generates an Astro project with EmDash pre-configured. The scaffolder lets you choose from blog, marketing, or portfolio templates.

## Preparing EmDash for Production

EmDash requires server-side rendering — it's a full-stack CMS, not a static site generator. Your Astro config must use the Node.js adapter in standalone mode.

### Configure the Astro Adapter

Open your `astro.config.mjs` and verify the Node.js adapter is set to `standalone`:

```
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import emdash from 'emdash';

export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone',
  }),
  integrations: [emdash()],
});
```

The `standalone` mode is critical — it produces a self-contained server entry point at `./dist/server/entry.mjs` that you can run directly with Node.js. The alternative `middleware` mode is for platforms like Express where you manage the HTTP server yourself, but standalone is simpler for [DeployHQ](https://www.deployhq.com) deployments.

### Generate Authentication Secrets

EmDash needs two secrets for production: one for signing session cookies and another for preview URLs. Generate them locally:

```
npx emdash auth secret
```

This outputs a cryptographically secure random string. Run it twice — once for `EMDASH_AUTH_SECRET` and once for `EMDASH_PREVIEW_SECRET`. Save both values; you'll add them as environment variables in [DeployHQ](https://www.deployhq.com).

### Choose Your Database

EmDash supports several database backends. For a Node.js deployment via [DeployHQ](https://www.deployhq.com), the practical options are:

| Database | Best For | Configuration |
| --- | --- | --- |
| SQLite | Single-server deployments | Set `DATABASE_PATH` to a persistent directory |
| PostgreSQL | Multi-server or production-critical sites | Set `DATABASE_URL` connection string |
| libSQL (Turso) | Remote SQLite with replication | Set `LIBSQL_DATABASE_URL` and `LIBSQL_AUTH_TOKEN` |

SQLite is the simplest choice for a single VPS — no external database server needed. Just make sure the database file lives on a persistent volume, not in the deployment directory (since [DeployHQ](https://www.deployhq.com) replaces files on each deploy).

For SQLite, create a data directory outside your deployment path:

```
# On your server, create a persistent data directory
mkdir -p /var/data/emdash
chown deploy:deploy /var/data/emdash
```

Then set the environment variable to point there:

```
DATABASE_PATH=/var/data/emdash/emdash.db
```

### Add a Start Script

Verify your `package.json` includes a start script:

```
{
  "scripts": {
    "dev": "astro dev",
    "build": "astro build",
    "start": "node ./dist/server/entry.mjs",
    "bootstrap": "emdash init && emdash seed"
  }
}
```

The `build` command compiles your Astro project into the `dist/` directory. The `start` command runs the compiled server. [DeployHQ](https://www.deployhq.com) will execute `build` during deployment and your process manager will run `start` on the server.

## Setting Up DeployHQ

With your EmDash project in Git and your server ready, connect everything through [DeployHQ](https://www.deployhq.com).

### Create a New Project

1. Log in to [DeployHQ](https://www.deployhq.com/) and click **New Project**
2. Name it (e.g., EmDash Blog) and select the server region closest to your deployment target
3. Connect your Git repository — [DeployHQ](https://www.deployhq.com) supports GitHub, GitLab, Bitbucket, and self-hosted Git servers

### Configure the Server

Add your deployment server under **Servers** in the project settings:

1. Click **New Server**
2. Choose **SSH/SFTP** as the protocol
3. Enter your server hostname, SSH port, and the deploy user credentials
4. Set the **Deployment Path** to where your application lives, for example `/var/www/emdash`
5. Test the connection to verify [DeployHQ](https://www.deployhq.com) can reach your server

### Configure the Build Pipeline

This is where [DeployHQ](https://www.deployhq.com) handles your Node.js build. Navigate to **Build Pipeline** in your project settings:

1. Enable the [build pipeline](https://www.deployhq.com/features/build-pipelines)
2. Select **Node.js 22** as the build environment
3. Add the build commands:

```
npm ci
npm run build
```

Using `npm ci` instead of `npm install` ensures a clean, reproducible install from your lockfile — important for consistent deployments.

DeployHQ's build pipeline runs these commands in an isolated container, compiles your Astro project, and then deploys only the output files to your server. This means your server doesn't need to run `npm install` or `npm run build` — it just receives the compiled application.

### Set Environment Variables

Under **Environment Variables** in your project settings, add:

| Variable | Value |
| --- | --- |
| `EMDASH_AUTH_SECRET` | Your generated auth secret |
| `EMDASH_PREVIEW_SECRET` | Your generated preview secret |
| `DATABASE_PATH` | `/var/data/emdash/emdash.db` (for SQLite) |
| `HOST` | `0.0.0.0` |
| `PORT` | `4321` (or your preferred port) |

If you're using PostgreSQL instead, replace `DATABASE_PATH` with `DATABASE_URL` pointing to your connection string.

[DeployHQ](https://www.deployhq.com) encrypts environment variables at rest, so your secrets are protected. These variables are available during the build step and can also be written to a `.env` file on the server if your application reads from one.

### Configure Deployment Exclusions

Not every file needs to reach your server. Under **Excluded Files** , add:

```
src/
tests/
node_modules/
.git/
tsconfig.json
astro.config.mjs
README.md
```

Wait — you actually need `node_modules` on the server because `dist/server/entry.mjs` requires runtime dependencies. There are two approaches:

**Approach A — [Deploy](https://www.deployhq.com) node\_modules (simpler):** Don't exclude `node_modules`. [DeployHQ](https://www.deployhq.com) transfers the full project including dependencies. This is slower but requires no server-side commands.

**Approach B — Install on server (cleaner):** Exclude `node_modules` and add a [post-deploy SSH command](https://www.deployhq.com/support/projects-and-servers/ssh-commands) to install production dependencies:

```
cd /var/www/emdash && npm ci --omit=dev
```

Approach B is preferred for larger projects because it only installs production dependencies, keeping the server lean.

## Deploying EmDash

### First Deployment

Your first deployment needs extra setup because the database doesn't exist yet. After the initial deploy completes:

1. SSH into your server
2. Navigate to your deployment directory
3. Run the bootstrap command to initialise the database:

```
cd /var/www/emdash
npm run bootstrap
```

This creates the database schema and optionally seeds sample content. You only need to run this once.

1. Start the application:

```
# Using PM2 (recommended)
pm2 start dist/server/entry.mjs --name emdash

# Or using systemd (see next section)
```

1. Visit `http://your-server:4321/_emdash/admin` to complete the setup wizard — set your site title, tagline, and register your admin passkey.

### Set Up a Process Manager

For production, use PM2 or systemd to keep EmDash running and restart it on crashes or reboots.

**PM2 ecosystem file** (`ecosystem.config.cjs`):

```
// ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'emdash',
    script: './dist/server/entry.mjs',
    env: {
      HOST: '0.0.0.0',
      PORT: 4321,
      DATABASE_PATH: '/var/data/emdash/emdash.db',
      NODE_ENV: 'production',
    },
    instances: 1,
    autorestart: true,
    max_memory_restart: '512M',
  }],
};
```

Note: if you're using SQLite, keep `instances: 1`. SQLite doesn't handle concurrent writes from multiple processes well. For multi-instance deployments, switch to PostgreSQL.

**systemd service** (`/etc/systemd/system/emdash.service`):

```
[Unit]
Description=EmDash CMS
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/emdash
ExecStart=/usr/bin/node dist/server/entry.mjs
Restart=on-failure
Environment=NODE_ENV=production
Environment=HOST=0.0.0.0
Environment=PORT=4321
Environment=DATABASE_PATH=/var/data/emdash/emdash.db
EnvironmentFile=/var/www/emdash/.env

[Install]
WantedBy=multi-user.target
```

### Automate Restarts with Post-Deploy Commands

After each deployment, EmDash needs to restart so it picks up the new code. Add a post-deploy SSH command in [DeployHQ](https://www.deployhq.com) under **SSH Commands → After Deployment** :

```
cd /var/www/emdash && pm2 restart emdash
```

Or for systemd:

```
sudo systemctl restart emdash
```

This ensures every successful deployment automatically restarts the application without manual intervention.

### Subsequent Deployments

After the initial setup, the deployment workflow is:

1. Push code changes to your Git repository
2. [DeployHQ](https://www.deployhq.com) detects the push (via webhook) and starts a deployment
3. The build pipeline runs `npm ci && npm run build`
4. Compiled files are transferred to your server
5. The post-deploy command restarts the application

You can also configure [DeployHQ](https://www.deployhq.com) to [deploy automatically on every push](https://www.deployhq.com/features/automatic-deployments) to your main branch, or keep deployments manual and trigger them from the [DeployHQ](https://www.deployhq.com) dashboard.

## Deployment Architecture

Here's how the pieces fit together:

```
flowchart LR
    A[Git Push] --> B[DeployHQ]
    B --> C[Build Pipeline<br/>npm ci + astro build]
    C --> D[Transfer Files<br/>via SSH]
    D --> E[VPS / Cloud Server]
    E --> F[PM2 / systemd<br/>Restart]
    F --> G[EmDash Running<br/>port 4321]
    G --> H[Reverse Proxy<br/>nginx / Caddy]
    H --> I[Public Traffic<br/>HTTPS on port 443]
```

[DeployHQ](https://www.deployhq.com) handles steps A through F. You configure the reverse proxy once during initial server setup.

## Setting Up a Reverse Proxy

In production, you'll want a reverse proxy in front of EmDash to handle HTTPS, compression, and static asset caching. Here's a minimal nginx configuration:

```
server {
    listen 443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:4321;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}
```

If you prefer Caddy, the configuration is even simpler:

```
your-domain.com {
    reverse_proxy localhost:4321
}
```

Caddy handles TLS certificates automatically via Let's Encrypt — no manual certificate setup needed.

## Troubleshooting

### Build Fails with Cannot find module Errors

This usually means dependencies aren't installed correctly. Make sure your `package-lock.json` is committed to Git. DeployHQ's build pipeline runs `npm ci`, which requires a lockfile. If you're using pnpm locally, either switch [DeployHQ](https://www.deployhq.com) to use `pnpm install && pnpm build` or add a `package-lock.json` alongside.

### Application Starts but Admin Panel Returns 404

EmDash's admin routes live under `/_emdash/admin`. Make sure your Astro config has `output: 'server'` — if it's set to `'static'` or `'hybrid'`, the server-side routes won't work.

### Database Errors After Deployment

If you're using SQLite and see database is locked or no such table errors:

- Verify `DATABASE_PATH` points to a directory outside the deployment path (so it persists between deployments)
- Make sure the deploy user has write permissions to the database directory
- Run `npm run bootstrap` if the database hasn't been initialised yet

### Passkey Authentication Fails

Passkeys require HTTPS. If you're testing over plain HTTP, passkey registration and login will fail. Set up your reverse proxy with TLS first, or use the dev bypass endpoint for initial testing:

```
https://your-domain.com/_emdash/api/setup/dev-bypass?redirect=/_emdash/admin
```

Remove this bypass in production by ensuring `NODE_ENV=production` is set.

### Deployment Succeeds but Site Shows Old Content

The post-deploy restart command might not be running. Check the [DeployHQ](https://www.deployhq.com) deployment log for SSH command output. Also verify that PM2 or systemd is actually restarting the correct process — run `pm2 list` or `systemctl status emdash` on the server to confirm.

## Frequently Asked Questions

### Can I use DeployHQ's atomic deployments with EmDash?

Yes. DeployHQ's [zero downtime deployments](https://www.deployhq.com/features/zero-downtime-deployments) work well with EmDash. Each deployment goes into a new directory, and a symlink switches to the new version atomically. Just make sure your `DATABASE_PATH` and any uploaded media point to a shared directory outside the release path so they persist across deployments.

### Does EmDash work with DeployHQ's build pipeline caching?

The build pipeline caches `node_modules` between deployments, so subsequent builds are faster since npm only installs changed dependencies. Astro's build output isn't cached (it rebuilds each time), but the dependency caching alone can cut build times significantly.

### Can I deploy EmDash to shared hosting via FTP?

EmDash requires a long-running Node.js process, which most shared hosting plans don't support. You need a VPS, cloud instance, or container hosting where you can run `node dist/server/entry.mjs` persistently. [DeployHQ](https://www.deployhq.com) supports [FTP and SFTP deployment](https://www.deployhq.com/support/projects-and-servers/server-types) protocols, but the server itself must support Node.js 22+.

### How do I handle database migrations during deployment?

EmDash automatically runs migrations when the application starts (for SQLite, libSQL, and PostgreSQL). There's no separate migration step needed in your deployment pipeline. The restart via PM2 or systemd triggers the migration check on boot.

### Can multiple team members deploy independently?

Yes. [DeployHQ](https://www.deployhq.com) supports team collaboration with role-based permissions. You can restrict who can trigger deployments while giving everyone read access to deployment logs. Combined with DeployHQ's [deployment approvals](https://www.deployhq.com/features), you can require sign-off before production deployments go live.

## Next Steps

You now have a production EmDash deployment powered by Git-based automation through [DeployHQ](https://www.deployhq.com). Every push to your repository triggers a clean build and deployment — no manual file transfers, no SSH-ing into servers to run commands.

From here, you might want to:

- Set up [deployment notifications](https://www.deployhq.com/support/projects-and-servers/notifications) to get Slack or email alerts on each deploy
- Configure a staging server in [DeployHQ](https://www.deployhq.com) to test changes before they hit production
- Explore EmDash's plugin system to extend your CMS with custom functionality
- Add a [build pipeline](https://www.deployhq.com/features/build-pipelines) step for running tests before deployment

Ready to automate your EmDash deployments? [Sign up for DeployHQ](https://www.deployhq.com/signup) and connect your repository in minutes.

* * *

If you have questions about deploying EmDash or any other framework with [DeployHQ](https://www.deployhq.com), reach out to us at [support@deployhq.com](mailto:support@deployhq.com) or on [Twitter/X @deployhq](https://x.com/deployhq).

