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, 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.
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.
# 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/)
- Deploy updates (https://docs.emdashcms.com/deployment/updating/)
- Verify caching (https://docs.emdashcms.com/deployment/object-cache/) and test performance / load handling
- 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
- ...
What would you test first? How is your first impression of EmDash?
More guides
- Official docs: Deploy to Node.js - docs.emdashcms.com
- How to Deploy EmDash with DeployHQ
- 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





