v0.1.0
Zero-dependency blog server — Zig stdlib only. Serves Markdown posts as HTML with
optional hot-reload SSE. Single static binary, fits in a FROM scratch Docker image.
zig build runServer listens on 0.0.0.0:8420 by default. Open http://127.0.0.1:8420 locally.
zig build test
zig build # binary in zig-out/bin/docker build -t massless .
docker run --rm -p 8420:8420 -v ./posts:/posts:ro -v ./public:/public:ro masslessImage size: ~100 kB (FROM scratch).
Mounted posts/ and public/ directories must be readable by the container
user (UID 65532). Use chmod -R a+r posts public if needed.
Place Markdown files in posts/ with format:
YYYY-MM-DD-slug.md
Or with a timestamp:
YYYY-MM-DD-HH-MM-SS-slug.md
Slug: lowercase letters, digits, hyphens only. Max post size: 1 MiB. Dates are validated (month lengths, leap years).
Frontmatter (optional):
---
title: My Post
---
Body text here.
Posts are listed newest-first on the homepage. All valid posts are shown.
/posts/<slug> serves an individual post. /posts redirects to /.
Place files in public/ — served at /public/<path>. Path traversal is
blocked. Max file size: 2 MiB. Oversized or unreadable files return 404.
- Headings (
#through######) - Paragraphs
- Bullet and ordered lists
*italic*/**bold**- Inline
`code` - Links
[text](url)and images - Thematic breaks (
---)
HTML is escaped. Unsafe URL protocols (javascript:, data:, vbscript:) are blocked.
Safe schemes (http, https, mailto) and relative paths are allowed.
Only GET and HEAD methods are accepted. All others return 405 and the connection is closed immediately (body is not read). Query strings are stripped before routing. HTML responses include:
Content-Security-Policy(script-src depends on hot_reload mode)X-Content-Type-Options: nosniffReferrer-Policy: no-referrer
MIT
Edit src/config.zig:
pub const site_title = "massless";
pub const bind_addr = "0.0.0.0";
pub const port: u16 = 8420;
pub const hot_reload = true;
pub const max_post_bytes: usize = 1024 * 1024;
pub const max_public_file_bytes: usize = 2 * 1024 * 1024;Hot reload: when hot_reload = true, the server polls posts/ every 3s
and pushes changes to browsers via SSE on /events. Set hot_reload = false
to disable — /events returns 404 and no SSE script is injected.
| Metric | Native (ReleaseSmall) | Docker (FROM scratch) |
|---|---|---|
| Binary size | 193 KB | 205 KB (uncompressed layer) |
| Image size | — | ~100 KB (compressed) |
| RSS (idle) | ~890 KB | ~1.6 MiB |
| RSS (after requests) | ~910 KB | ~1.8 MiB |