EmDash is a new CMS made by Cloudflare, which describes itself as the „spiritual successor to WordPress (that solves plugin security)“ and as „an open source Astro CMS built for humans and agents“.
It can be deployed via Cloudflare (obviously), but selfhosting is also possible.
Here is a step-by-step guide to deploy EmDash CMS on a fresh Hetzner VPS using Node.js, SQLite, Caddy webserver, PM2 (node process manager), and sslip.io for automatic SSL certificates without needing a custom domain name (for now).
Beware: This is just for demo purposes, security needs to be checked. Use a hetzner firewall and only open port 22 and 443 for example.
For this tutorial, I quickly purchased a Hetzner CX23 VPS, Ubuntu 26.
Connect to the VPS
ssh root@<IP-of-hetzner-VPS>
Install Node.js & pnpm
# Download and install nvm (Node Version Manager):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash
# Load nvm into the current shell session in lieu of restarting the terminal
\. "$HOME/.nvm/nvm.sh"
# Download and install Node.js version 24:
nvm install 24
# Verify the Node.js version prints "v24.22.0":
node -v
# Enable and install pnpm via Node's corepack:
corepack enable pnpm
# Verify the installed pnpm version:
pnpm -v
Install Caddy webserver
# 1. Update package lists and install prerequisite packages for Caddy (curl and gnupg)
apt update && apt install -y curl gnupg
# 2. Download and add the official Caddy GPG signing key to secure apt repositories
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
# Add the Caddy official repository source list configuration
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
# 3. Update APT package lists again to include the new Caddy repository and install Caddy web server
apt update
apt install -y caddy
Check that the Caddy background service is active and running correctly: systemctl status caddy
Git pull the demo project
For this demo, I simply installed the marketing starter via pnpm create emdash@latest ...
... and pushed it to a public GitHub repository on my account (
https://github.com/mandrasch/emdash-demo-marketing-starter).
This is what we'll deploy:
# Create a central web root directory for hosting websites
mkdir /var/www
# Change current directory into the web root
cd /var/www
# Clone the EmDash marketing starter demo project repo
git clone https://github.com/mandrasch/emdash-demo-marketing-starter.git
# Move inside the cloned project directory
cd emdash-demo-marketing-starter
# Install project dependencies using pnpm and compile the production application bundle to serve the site later
pnpm i && pnpm run build
Setup the demo project
# Generate a secure random cryptographic key for encrypting plugin secrets in EmDash
EMDASH_KEY=$(pnpm dlx emdash secrets generate)
# Fetch the server's public IPv4 address from an external API and store it in a variable
SERVER_IP=$(curl -s https://api.ipify.org)
# Create and write the runtime environment configuration file (.env) with server settings
cat << EOF > /var/www/emdash-demo-marketing-starter/.env
PORT=4321
HOST=0.0.0.0
EMDASH_SITE_URL=https://${SERVER_IP}.sslip.io
DATABASE_PATH=/var/www/emdash-demo-marketing-starter/data/emdash.db
EMDASH_ENCRYPTION_KEY=${EMDASH_KEY}
EOF
# Create local directories required for storing SQLite data files and uploaded media assets
mkdir -p /var/www/emdash-demo-marketing-starter/data/uploads
Install pm2
PM2 is a daemon process manager for Node.js applications.
# Configure pnpm global bin paths into the shell configuration and reload it
pnpm setup && source ~/.bashrc
# Install PM2 globally as a Node.js process manager to keep apps running in the background
pnpm add -g pm2
Run our site (via pm2)
# Change directory back to the project folder before starting the process
cd /var/www/emdash-demo-marketing-starter
# Start the EmDash production server instance via PM2 using the Node.js binary and environment file
pm2 start /root/.nvm/versions/node/v24.22.0/bin/node --name "emdash" -- --env-file=.env ./dist/server/entry.mjs
The command pm2 list now shows the running emdash site:
See the pm2 cheatsheet for all commands.
Configure pm2 to launch on server restarts
# Generate and configure a systemd startup script so PM2 automatically restarts apps on server reboots
pm2 startup systemd -u root --hp /root
# Save the currently running PM2 process list so it persists across reboots
pm2 save
Create the Caddy configuration
# Fetch the public IP address again to ensure it's available for the Caddyfile configuration block
SERVER_IP=$(curl -s https://api.ipify.org)
# Create a Caddyfile configuration to reverse-proxy traffic from your sslip.io domain to port 4321
cat << EOF | sudo tee /etc/caddy/Caddyfile
${SERVER_IP}.sslip.io {
reverse_proxy localhost:4321
}
EOF
# Reload Caddy to apply the new reverse proxy configuration and automatically generate an SSL certificate
sudo systemctl reload caddy
Access your site and finish initial installation
Open your browser to test the public site, the images won't be there yet: https://<YOUR-SERVER-IP>.sslip.io
Create the admin account:
https://<YOUR-SERVER-IP>.sslip.io/_emdash/admin
In step 3, you can generate a passkey for your admin account.
(There is no current option to use a regular password out of the box, see my recent bluesky post for more information about it.)
After the finished setup, the images will be also visible on https://<YOUR-SERVER-IP>.sslip.io:
And you can edit your content in the backend:
I could not find the setting to lock down the Admin > Content Types section for now, but I'll re-check out the docs.
Where to go from here
This was fairly simple, but to really evaluate it professionally we would need to check the following things:
- Change things in the git repo, deploy it again (and restart the site)
- Deployment of db structure changes (see e.g. https://docs.emdashcms.com/deployment/core-migrations/)
- Pull live db + uploaded files to local dev, to test changes on real content - how?
- Deploy updates (https://docs.emdashcms.com/deployment/updating/)
- Verify caching (https://docs.emdashcms.com/deployment/object-cache/), evaluate if there is full static HTML caching available?
- Test performance / load handling - how much can one instance take?
- Evaluate if S3 compatible storage is needed instead of local file disk storage for uploads, e.g. selfhost https://github.com/pgsty/silo (since MinIO has been terminated)
- Add more CMS users
- Use a real domain to run your site
- Change db type to Postgres - benefits?
- ...
What would you test first? And what is your first impression of EmDash?
More guides
- Official docs: Deploy to Node.js - docs.emdashcms.com
- How to Deploy EmDash with DeployHQ - deployhq.com
- Mastering EmDash: Setup, Migration, Hosting, and Databases for the Astro-Native CMS - rogerstringer.com
- Self-hosting EmDash CMS on Node.js: what you own - emdashbits.com








Top comments (0)