· Jane Developer · —

Building a Blog with Rust and S3

A deep dive into the architecture of Papaya CMS and how it leverages Rust and S3 for a modern blogging platform

Building a Blog with Rust and S3

Building a Blog with Rust and S3

When we set out to build Papaya CMS, we had a clear vision: a lightweight, fast, and flexible blogging platform that doesn’t require a database.

Architecture Overview

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│  Axum HTTP  │────▶│  In-Memory  │
│  (Browser)  │     │   Server    │     │    Cache    │
└─────────────┘     └─────────────┘     └─────────────┘
                           │                    │
                           ▼                    ▼
                    ┌─────────────┐     ┌─────────────┐
                    │    S3       │     │   Tera      │
                    │  (Content)  │     │  Templates  │
                    └─────────────┘     └─────────────┘

The Stack

ComponentTechnologyReason
Web FrameworkAxumErgonomic, performant, type-safe
TemplatingTeraJinja2-like, familiar syntax
Markdownpulldown-cmarkFast, GFM-compliant
StorageAWS S3Scalable, durable, CDN-ready
CacheArc<RwLock<>>Zero-copy reads, thread-safe
Configconfig-rsLayered, env-override support
LoggingtracingStructured, JSON output

Content Pipeline

1. Markdown with Frontmatter

---
title: "My Post"
slug: "my-post"
date: "2024-01-15"
tags: ["rust", "web"]
cover_image: "https://bucket.s3.amazonaws.com/cover.jpg"
---

# Content here...

2. Server Boot Sequence

// 1. Load configuration
let config = Config::load()?;

// 2. Initialize S3 client
let s3 = S3Client::new(&config).await?;

// 3. Initialize template engine
let templates = TemplateEngine::new(&args).await?;

// 4. Create cache
let cache = Cache::new();

// 5. Hydrate cache from S3
cache.refresh(&s3, &config).await?;

3. Request Flow

GET /posts/my-post
       │
       ▼
Cache lookup by slug (O(1) via HashMap)
       │
       ▼
S3 fetch markdown/my-post.md
       │
       ▼
Parse frontmatter + body
       │
       ▼
Markdown → HTML (pulldown-cmark)
       │
       ▼
Tera render post.html
       │
       ▼
Return HTML response

Performance Characteristics

  • Cache reads: Zero-allocation, lock-free for readers
  • Cache writes: Single writer, blocks readers briefly
  • S3 calls: Only on cache miss or refresh
  • Template rendering: Pre-compiled Tera templates
  • Memory: ~1KB per post in cache

Custom Themes

Themes are loaded at container startup from a Git repository:

docker run \
  -e PAPAYA_TEMPLATE_REPO=https://github.com/user/themes \
  -e PAPAYA_TEMPLATE_NAME=minimal \
  papaya-cms

Template structure:

themes-repo/
└── templates/
    └── minimal/
        ├── home.html
        └── post.html

Deployment

Docker (Recommended)

# Multi-stage build for minimal image
FROM rust:1.82-slim as builder
# ... build steps ...

FROM debian:bookworm-slim
COPY --from=builder /app/target/release/papaya-backend /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

Environment Variables

VariableRequiredDefaultDescription
S3__ENDPOINTYes-S3 endpoint URL
S3__BUCKETYes-Bucket name
S3__ACCESS_KEYYes-Access key ID
S3__SECRET_KEYYes-Secret access key
API__REFRESH_TOKENYes-Cache refresh token
PAPAYA_TEMPLATE_REPONopapaya-cmsTemplate repo URL
PAPAYA_TEMPLATE_NAMENodefaultTemplate folder name

Future Enhancements

  • Search functionality
  • RSS/Atom feeds
  • Webhook notifications
  • Multi-language support
  • Plugin system

Built with ❤️ using Rust and Papaya CMS