--- title: "A fast blogging platform with Go, Nuxt, Caddy and Docker. " description: "I built Scrib.ee, a clean blogging platform with Go, Nuxt, Caddy, Docker, Redis, MySQL, and Meilisearch. It focuses on simple publishing, SEO-ready blogs, and Cleo, an AI reading assistant without AI bloat." slug: "a-fast-blogging-platform-with-go-nuxt-caddy-and-docker" published: true read_time: 5 created_at: "2026-07-09 20:49:58.429 +0000 UTC" updated_at: "2026-07-10 09:46:53.857 +0000 UTC" author: "Martin Binder" author_url: "https://scrib.ee/_id/martin" tags: - "Featured" --- # A fast blogging platform with Go, Nuxt, Caddy and Docker. I built Scrib.ee, a clean blogging platform with Go, Nuxt, Caddy, Docker, Redis, MySQL, and Meilisearch. It focuses on simple publishing, SEO-ready blogs, and Cleo, an AI reading assistant without AI bloat. ![https://media.scrib.ee](scribee.webp/276354b7-c6d8-4a2e-9268-94382f83b02f) ## AI without the bloat Scrib.ee also includes **Cleo**, an AI reading assistant inside blog posts. But I don’t want Scrib.ee to become another AI content generator. The goal is not to generate endless content. I want AI to help only where it is actually useful: helping readers understand posts better, asking questions about the article, and getting more value from what they are already reading. ## Tech stack The current stack is: - Go backend - Nuxt dashboard - Nuxt blog frontend - Caddy reverse proxy - Docker - Docker Compose - MySQL - Redis - Meilisearch - GitHub Actions CI/CD - VPS deployment The app is split into multiple services: ```txt app/ ├── server/ # Go API ├── client/ # Nuxt dashboard ├── blogclient/ # Nuxt public blog frontend ├── sitemap/ # XML sitemap generator ├── dbadmin/ # database admin utility └── docker/ └── caddy/ # reverse proxy and routing ``` ## Architecture Scrib.ee has one main Go API and two Nuxt frontends. The `client` app is the dashboard where users manage their blog. The `blogclient` app renders the public blogs. Both frontends talk to the same Go backend. Caddy sits in front of everything and handles routing, domains, subdomains, and HTTPS. ```txt User ↓ Caddy ↓ ┌──────────────┬────────────────┐ │ Dashboard │ Public Blogs │ │ Nuxt client │ Nuxt blogclient │ └──────┬───────┴───────┬────────┘ ↓ ↓ Go API ↓ ↓ ↓ MySQL Redis Meilisearch ``` ## Docker-native deployment Everything runs with Docker and Docker Compose. For production, I can start the app with: ```bash docker compose up -d ``` For development, I use a separate compose file: ```bash make dev ``` The Makefile also includes commands for common tasks: ```makefile env: @cp -n .env.example .env || true dev: @docker compose -f compose-dev.yaml up --build prod: @docker compose up -d deploy: @git pull origin main @APP_ENV=prod docker compose up -d --build @docker compose exec sitemap ./sitemap-worker generate @docker image prune -f logs: @docker compose logs -f $(SERVICE) ps: @docker compose ps down: @docker compose down test: @go test -C ./server ./... lint: @cd ./server && golangci-lint run fmt: @cd ./server && go fmt @cd ./client && pnpm run format @cd ./blogclient && pnpm run format ``` ## CI/CD with GitHub Actions I use GitHub Actions to build and push Docker images to GitHub Container Registry. The workflow checks which folders changed and only rebuilds the affected services. For example: - changes in `server/**` rebuild the Go API - changes in `client/**` rebuild the dashboard - changes in `blogclient/**` rebuild the public blog frontend - changes in `sitemap/**` rebuild the sitemap service There is also a manual `force_rebuild` option if I want to rebuild everything from scratch. After the images are built, the workflow connects to my VPS over SSH and runs: ```bash cd /srv/app git pull docker compose pull APP_ENV=prod APP_VERSION= docker compose up -d docker image prune -a -f ``` It is simple, but it works well for a bootstrapped project. ## SEO and sitemaps SEO is an important part of Scrib.ee. Each blog can generate its own sitemap. The sitemap service is written in Go and creates XML sitemap files for published posts and tags. It supports splitting large sitemaps into multiple files, using the standard limit of 50,000 URLs per file. A simplified version looks like this: ```go const maxURLsPerFile = 50000 ``` For each blog with sitemap enabled, the service: 1. loads the blog domain or subdomain 2. fetches published posts 3. fetches tags 4. writes sitemap XML files 5. writes a sitemap index The generated URLs include posts like: ```txt https://example.com/posts/my-post ``` And topics like: ```txt https://example.com/topic/go ``` ## Themes with CSS variables For blog customization, I use CSS variables. This keeps themes simple and flexible without needing a complex theme engine. A blog can have different colors, styles, and visual settings, while the frontend still stays fast and maintainable. The goal is to let users customize their blog without turning Scrib.ee into a huge website builder. ## Local development For local development, [Scrib.ee](https://Scrib.ee) uses subdomain-based routing. I add local domains to `/etc/hosts`: ```txt 127.0.0.1 scribee.local 127.0.0.1 watches.scribee.local blog.scribee.local ``` Then I can open: ```txt http://scribee.local ``` for the dashboard, and: ```txt http://watches.scribee.local ``` for a local test blog. ## Current stage [Scrib.ee](https://Scrib.ee) is still early, but already has the main structure working: - dashboard - public blog frontend - Go backend - Docker deployment - CI/CD - sitemap generation - caching - themes - Markdown posts - Cleo AI reading assistant I’m preparing for a bigger launch later, but first I want to collect feedback from developers, creators, bloggers, and indie makers. ## What I would love feedback on I would really appreciate feedback on: - the idea - the technical architecture - the UX - the positioning - the SEO features - the AI reading assistant - what would make you use a blogging platform today ## Final thoughts I don’t want [Scrib.ee](https://Scrib.ee) to become a bloated website builder or another AI content machine. I want it to be a clean, fast place where people can write, publish, and share ideas — with AI used only where it actually helps the reader. Thanks for reading.