When EmDash launched, I was intrigued by the hype. The world of CMS platforms has seemed pretty static for a while, so a big new launch caught my eye. And I thought I could use it as the kick-in-the-pants I needed to start writing and hosting my own site again.
Self-Hosting
When EmDash was released as a TypeScript-first alternative to traditional blogging platforms, the default recommendation was to deploy it to Cloudflare workers using D1 and R2. While that serverless edge architecture works well for zero-maintenance setups, I prefer having complete control over the underlying environment.
Instead of going the serverless route, I decided to self-host this site on Render using a traditional containerized configuration. Below is an overview of the current infrastructure, packaging workflow, and how I track metrics without deploying client-side tracking scripts.
Application Packaging: EmDash & Docker
EmDash operates as an Astro integration. Because it runs reliably on a standard Node.js runtime with a local SQLite database, wrapping it in a container is straightforward.
To handle deployments cleanly on Render, the project uses a multi-stage Dockerfile. The build stage installs dependencies and executes astro build. The production stage copies only the built static assets and necessary production node modules, keeping the final image footprint minimal. Persistent volume mounts on Render ensure the SQLite database files are preserved across deployments.
Routing and SSL: Caddy
I used to be all about using Nginx on all-the-things. Obviously it’s run a large portion of the Internet for quite some time. However, Caddy has slowly crept in as a robust, easily configurable alternative. Nginx is great for the power it provides, but sometimes you just need to sit down with a nice easy configuration file without spending hours remembering all of the documentation you’ve read. So I put a Caddy instance directly in front of the application container.
Aside from handling ingress traffic, my Caddyfile contains an explicit log directive, which is the core component of the site’s metrics pipeline.
Server-Side Analytics: GoAccess
EmDash does not include a native analytics dashboard and leaves tracking implementation up to the developer. Most platforms suggest dropping a JavaScript tracking snippet into the global layout file. To avoid the performance overhead and privacy implications of client-side tracking, I handle analytics entirely on the server using GoAccess.
GoAccess is an open-source, real-time log analyzer. A cron job running on the host regularly parses the Caddy structured access.log file. It processes the raw server logs and outputs a static, self-contained HTML dashboard.
This setup provides standard visibility into:
- Unique visitor and bandwidth trends
- Top requested articles and paths
- HTTP status codes (useful for monitoring 404 errors)
- Referring URLs
Because this processing happens strictly on the backend, there are no tracking cookies, no scripts executed in the user's browser, and zero client-side performance penalties.
Deployment on Render
I elected to deploy everything on Render. This was mostly out of familiarity - I already used Renders docker-and-Github-based deploys for other projects, both personal and professional. That familiarity, plus the general ease of deploying containers from a git project, made Render a dead-simple no-brainer.
Conclusion
Building modern sites does not require adopting a sprawling serverless ecosystem. Packaging an Astro-based CMS into a lightweight container, putting it behind an automated proxy, and parsing standard logs for metrics creates a fast, isolated setup that is cheap to run and easy to maintain.
No comments yet