DEV Community

Martin Binder
Martin Binder

Posted on

I built Scrib.ee β€” a fast blogging platform with Go, Nuxt, Caddy and Docker.

Scrib.ee screenshot

Hey everyone πŸ‘‹

I’m Martin, a full-stack developer from Hungary.

I’ve been working on Scrib.ee, a clean and fast blogging platform for people who want to publish without fighting setup, themes, hosting, or plugins.

The goal is simple: make blogging feel simple again.

Why I built it

Blogging platforms often feel too complicated.

Some are too heavy.

Some are too technical.

Some are too focused on newsletters.

Some make you spend more time configuring things than actually writing.

I wanted to build something focused:

  • create a blog
  • write posts
  • publish
  • customize the look
  • have SEO basics ready from the start

That’s it.

What Scrib.ee does

Scrib.ee gives users a dashboard where they can manage their blog, write posts, customize themes, and publish content.

Each blog has its own public frontend, so the writing/admin experience and the reader experience are separated.

The platform supports:

  • blog creation
  • post writing
  • Markdown-based content
  • multiple blog formats
  • SEO-ready pages
  • sitemap generation
  • custom themes using CSS variables
  • caching
  • subdomain-based blogs
  • AI-guided reading with Cleo

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:

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
Enter fullscreen mode Exit fullscreen mode

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.

User
  ↓
Caddy
  ↓
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ Dashboard    β”‚ Public Blogs    β”‚
 β”‚ Nuxt client  β”‚ Nuxt blogclient β”‚
 β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        ↓               ↓
             Go API
        ↓       ↓       ↓
      MySQL   Redis   Meilisearch
Enter fullscreen mode Exit fullscreen mode

Docker-native deployment

Everything runs with Docker and Docker Compose.

For production, I can start the app with:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

For development, I use a separate compose file:

make dev
Enter fullscreen mode Exit fullscreen mode

The Makefile also includes commands for common tasks:

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
Enter fullscreen mode Exit fullscreen mode

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:

cd /srv/app
git pull
docker compose pull
APP_ENV=prod APP_VERSION=<short-sha> docker compose up -d
docker image prune -a -f
Enter fullscreen mode Exit fullscreen mode

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:

const maxURLsPerFile = 50000
Enter fullscreen mode Exit fullscreen mode

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:

https://scrib.ee/posts/my-post
Enter fullscreen mode Exit fullscreen mode

And topics like:

https://scrib.ee/topic/go
Enter fullscreen mode Exit fullscreen mode

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 uses subdomain-based routing.

I add local domains to /etc/hosts:

127.0.0.1   scribee.local
127.0.0.1   watches.scribee.local blog.scribee.local
Enter fullscreen mode Exit fullscreen mode

Then I can open:

http://scribee.local
Enter fullscreen mode Exit fullscreen mode

for the dashboard, and:

http://watches.scribee.local
Enter fullscreen mode Exit fullscreen mode

for a local test blog.

Current stage

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 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.

Top comments (0)