- JavaScript 73%
- Shell 9.1%
- Makefile 6.9%
- Nunjucks 4.3%
- Dockerfile 4.2%
- Other 2.5%
Drift fixes after auditing both docs against actual code/config/compose:
CLAUDE.md:
- Add migrator service to Services table (was completely missing — 7th
service in docker-compose.yml, profile-gated to `migrate`)
- Update architecture diagram to show Redis (full profile) and migrator
- Add dedicated patches table with per-patch purpose for THIS repo
(routes.js: two-tier rate limiting + AP content negotiation,
error.js: stack trace suppression in production,
indieauth.js: redirect URI regex fix — different from cloudron's patches)
- Replace stale inline plugin lists with pointers to package.{core,full}.json
- Add new "Migration System" section documenting the bin/adapters/lib
architecture, Caddy bind-mount integration, workflow, and adapter status
- Document the Compose 2.39+ profile gate quirk that requires
`--profile redis` flag (else "service indiekit depends on undefined
service redis" error)
- Add migrate-* and release-* targets to Commands section
- Add Conversations to Core endpoints (was missing)
- Remove "Webmentions Proxy" reference (deprecated plugin, never installed)
README.md:
- Add `page` post type to Core profile table (was missing despite being in
package.core.json)
- Drop "Webmentions proxy" from Full profile description
- Note that Full profile requires Redis (Fedify mandate as of AP 2.2.0)
- Update architecture diagram to show Redis (full profile) and migrator
(one-shot, profile-gated)
- Add HTTP/3 :443/UDP port to architecture
- Expand Endpoints/Infra rows in Core table to reflect actual installed plugins
CHANGELOG.md:
- Add comprehensive 2026-05-08 entry covering:
* New migration system (migrator service + Hugo/Jekyll/microblog adapters
+ Caddy redirect bind-mount + make migrate-* targets)
* Plugin version bumps since the 2026-02-23 entry, including the major
AP plugin upgrade 2.0.9 → 3.13.6 (Fedify 2.2.0 + security patches)
* New plugins added (startup-gate, post-type-page, files, share, linkedin,
comments, readlater, blogroll, podroll)
* Documentation reorganization to drift-resistant source-of-truth pattern
|
||
|---|---|---|
| .github/workflows | ||
| ansible | ||
| config | ||
| docker | ||
| docs | ||
| eleventy-site@1b7b18fda1 | ||
| migration | ||
| .env.example | ||
| .gitignore | ||
| .gitmodules | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| docker-compose.full.yml | ||
| docker-compose.override.example.yml | ||
| docker-compose.yml | ||
| Makefile | ||
| README.md | ||
| TODO.md | ||
indiekit-deploy
Docker Compose + Ansible deployment for Indiekit, an IndieWeb server with Micropub support, static site generation, and POSSE syndication.
Architecture
Internet
|
┌──────┴──────┐
│ Caddy :443 │ Automatic HTTPS (Let's Encrypt)
│ :80 → :443 │ Static site + reverse proxy
│ :443/UDP │ HTTP/3
└──┬───────┬──┘
│ │
┌───────────┘ └──────────┐
▼ ▼
┌────────────────┐ ┌─────────────────┐
│ Indiekit:8080 │ │ Eleventy │
│ Micropub │──content──▶│ Watch + rebuild │
│ Auth, Admin │ volume │ Atomic releases │
└───┬─────────┬──┘ └─────────────────┘
│ │ │
▼ ▼ site volume → Caddy serves
┌──────┐ ┌──────┐
│Mongo │ │Redis │ ┌─────────────────┐
│ Data │ │Fedify│ │ Cron sidecar │
│ │ │KV+ │ │ Syndication 2m │
│ │ │cache │ │ Webmentions 5m │
└──────┘ └──────┘ └─────────────────┘
↑
└─ mandatory for full profile
(ActivityPub federation)
┌────────────────────────────────────────┐
│ Migrator (one-shot, profile=migrate) │
│ Jekyll/Hugo/micro.blog → Indiekit │
│ Activated via `make migrate-*` │
└────────────────────────────────────────┘
Services: MongoDB, Indiekit (Node.js), Eleventy (static site builder), Caddy (HTTPS reverse proxy), Cron (background jobs), Redis (cache; mandatory for full profile). Plus a one-shot Migrator service (profile-gated) for converting external static sites.
New to Indiekit? Read the full deployment guide — a step-by-step walkthrough covering server setup, DNS, configuration, first-run password creation, syndication, webmentions, and the full plugin set.
Quick Start (Docker Compose)
Prerequisites: Docker and Docker Compose v2 on a server with ports 80 and 443 open.
# 1. Clone this repo
git clone https://github.com/rmdes/indiekit-deploy.git
cd indiekit-deploy
# 2. Initialize the Eleventy theme submodule
make init
# 3. Configure your environment
cp .env.example .env
# Edit .env — set DOMAIN, SITE_URL, SITE_NAME, AUTHOR_NAME at minimum
# 4. Start all services
make up
# 5. Set your admin password
# Visit https://your-domain.com/session/login
# You'll see a "New password" page — create your password
# Indiekit displays a PASSWORD_SECRET hash (starts with $2b$...)
# Copy it into .env, escaping every $ as $$:
# PASSWORD_SECRET=$$2b$$10$$your-hash-here...
# Then restart: make restart
# 6. Log in at https://your-domain.com/session/login
Caddy automatically provisions a Let's Encrypt TLS certificate for your domain. Make sure your DNS A record points to your server before starting.
Using Pre-built Images (skip the build)
Pre-built images are automatically built on every commit and published to both Docker Hub and GitHub Container Registry (GHCR):
| Image | Description |
|---|---|
rmdes/indiekit-deploy-server |
Indiekit server — core plugin set |
rmdes/indiekit-deploy-server-full |
Indiekit server — full plugin set |
rmdes/indiekit-deploy-site |
Eleventy static site builder |
rmdes/indiekit-deploy-cron |
Cron sidecar (syndication + webmentions) |
rmdes/indiekit-deploy-migrator |
Migration tool (Jekyll/Hugo/micro.blog → Indiekit) |
GHCR alternatives are available at ghcr.io/rmdes/indiekit-deploy-*.
git clone https://github.com/rmdes/indiekit-deploy.git
cd indiekit-deploy
make init
cp .env.example .env # Edit with your values
docker compose pull # Pull pre-built images
docker compose up -d # Start — no build needed
For the full plugin set with pre-built images:
docker compose -f docker-compose.yml -f docker-compose.full.yml pull
docker compose -f docker-compose.yml -f docker-compose.full.yml up -d
Images are tagged :latest, :VERSION (e.g. 1.0.0-beta.25), and :sha-SHORT. To pin a specific version:
# In docker-compose.override.yml
services:
indiekit:
image: rmdes/indiekit-deploy-server:1.0.0-beta.25
If you prefer to build locally instead, use make build or make build-full.
Setting Your Admin Password
On first visit to /session/login, Indiekit shows a "New password" page:
- Create your password — choose something strong
- Copy the hash — Indiekit displays a
PASSWORD_SECRETvalue (e.g.,$2b$10$abc123...) - Escape the
$signs — Docker Compose uses$for variable substitution, so every$in the hash must be doubled to$$:Original: $2b$10$Eujjehrmx.K.n92T3SFLJe/... Escaped: $$2b$$10$$Eujjehrmx.K.n92T3SFLJe/... - Save it in
.env:PASSWORD_SECRET=$$2b$$10$$Eujjehrmx.K.n92T3SFLJe/... - Restart Indiekit —
make restartordocker compose restart indiekit - Log in — use your password at
/session/login
Plugin Profiles
Core (default)
Minimal plugin set for a functional IndieWeb blog:
| Category | Plugins |
|---|---|
| Post types | article, bookmark, like, note, photo, reply, repost, page (slash pages: /about, /now, /uses) |
| Preset | @rmdes/indiekit-preset-eleventy (permalink fix) |
| Store | @indiekit/store-file-system |
| Endpoints | Micropub, Syndicate, JSON Feed, Webmention.io, Webmention Sender, Conversations, LinkedIn (OAuth), Files, Share |
| Syndicators | Mastodon, Bluesky, LinkedIn, IndieNews |
| Infra | @rmdes/indiekit-startup-gate (defers plugin background tasks until first build completes) |
make up
Full
Core plus the rich @rmdes/* plugin set: GitHub activity, Funkwhale, Last.fm, YouTube, RSS reader, Microsub social reader, Podroll, Blogroll, Homepage builder, CV, Comments, Read-later, ActivityPub federation (Fedify), and extra post types (audio, event, jam, rsvp, video).
make up-full
The full profile requires Redis (mandatory for the ActivityPub plugin's Fedify KV store) and is started with --profile redis automatically by the Makefile.
Configuration
All configuration is done through the .env file. See .env.example for the full reference with documentation.
Required Variables
| Variable | Description | Example |
|---|---|---|
DOMAIN |
Your domain (used by Caddy for TLS) | example.com |
SITE_URL |
Full site URL | https://example.com |
SITE_NAME |
Site title | My IndieWeb Blog |
AUTHOR_NAME |
Your name | Jane Doe |
Syndication (optional)
Set the relevant env vars to enable POSSE syndication:
- Mastodon:
MASTODON_INSTANCE,MASTODON_USER,MASTODON_ACCESS_TOKEN - Bluesky:
BLUESKY_HANDLE,BLUESKY_PASSWORD - LinkedIn: Use the OAuth flow at
/linkedin, or setLINKEDIN_ACCESS_TOKENmanually
Indiekit Config
The Indiekit configuration lives in config/indiekit.config.js (core) and config/indiekit.config.full.js (full). On first run, the config is copied to the persistent volume at /data/config/indiekit.config.js. To update the config after first run, either:
- Edit the file in the volume:
make shell-indiekitthenvi /data/config/indiekit.config.js - Or delete the volume copy to re-copy from the image: remove
/data/config/indiekit.config.jsand restart
Ansible Deployment
For automated provisioning on a fresh server.
Prerequisites
- Ansible 2.12+ on your local machine
- A server running Ubuntu 22.04+ or Debian 12+
- SSH access with sudo privileges
- DNS A record pointing to the server
Setup
cd ansible
# 1. Create inventory from template
cp inventory.example inventory
# Edit inventory with your server IP and SSH user
# 2. Configure variables
# Edit group_vars/all.yml with your site settings
# For secrets, use ansible-vault or host_vars/
# 3. Provision and deploy
ansible-playbook -i inventory playbook.yml
Updating
ansible-playbook -i inventory playbook.yml --tags update
Migrating from another static site generator
Already running Hugo, Jekyll, or micro.blog? The bundled migration tool moves your existing posts, media, and old URLs into Indiekit without losing them.
cp -r ~/old-blog/* migration/input/
make migrate-detect # auto-detects the SSG layout
make migrate-convert FROM=hugo # transform → migration/staged/
make migrate-preview # diff against live volumes
make migrate-apply # copy into content + uploads volumes
docker compose restart caddy # activate URL redirects
Migrated posts serve at canonical Indiekit URLs
(/articles/2024/03/15/slug/); old URLs from your previous site
301-redirect to the canonical ones; media files keep their original
web paths.
- migration/README.md — overview, supported sources, classification rules
- docs/migration-from-hugo.md — step-by-step Hugo migration with edge cases and troubleshooting
Common Commands
make up # Start services (core profile)
make up-full # Start services (full profile)
make down # Stop all services
make logs # Follow all logs
make restart # Restart all services
make status # Show service status
make build # Rebuild images (no cache)
make shell-indiekit # Shell into Indiekit container
make shell-eleventy # Shell into Eleventy container
make backup # Backup all volumes to backups/
make restore FILE=backups/indiekit-*.tar.gz # Restore from backup
make update-theme # Pull latest Eleventy theme
make migrate-detect # Detect SSG in migration/input/
make migrate-convert FROM=hugo # Convert SSG → migration/staged/
make migrate-preview # Diff staged tree vs live volumes
make migrate-apply # Copy staged → live volumes (FORCE=1 to overwrite)
Eleventy Theme
The Eleventy theme is included as a Git submodule in eleventy-site/. It's built into the Eleventy Docker image at build time.
To use a different theme:
- Remove the submodule:
git submodule deinit eleventy-site && git rm eleventy-site - Add your theme:
git submodule add https://github.com/you/your-theme.git eleventy-site - Rebuild:
make build
To update the theme:
make update-theme
make build
make restart
SSL/TLS
Caddy handles HTTPS automatically via Let's Encrypt. Requirements:
- Your
DOMAINenv var must match your DNS A record - Ports 80 and 443 must be open and reachable from the internet
- Caddy stores certificates in the
caddy_dataDocker volume
For local development or environments behind another reverse proxy, you can override the Caddyfile to use HTTP only or internal TLS.
Backup & Restore
Backup
make backup
# Creates backups/indiekit-YYYYMMDD-HHMMSS.tar.gz containing:
# content/ — all your posts
# uploads/ — media files
# mongodb/ — database
# config/ — config + JWT secret
Restore
make restore FILE=backups/indiekit-20260207-120000.tar.gz
# Stops services, restores volumes, restarts
Updating
With pre-built images (recommended)
docker compose pull # Pull latest images
docker compose up -d # Restart with new images
Building locally
git pull
make update-theme # if theme has updates
make build # rebuild images
make up # restart with new images
Troubleshooting
Caddy won't start / TLS errors
- Ensure DNS A record points to your server IP
- Ensure ports 80 and 443 are open (check
ufw status) - Check Caddy logs:
docker compose logs caddy - Caddy needs port 80 for ACME HTTP challenge
Eleventy shows "Building site..."
- Eleventy is still building. Wait a minute and refresh.
- Check logs:
docker compose logs eleventy - If build fails, it shows "Blog coming soon" — check for template errors in logs
Posts don't appear on the site
- Eleventy watcher may need a moment to detect changes
- Check:
docker compose logs eleventyfor rebuild activity - The watcher auto-restarts with exponential backoff on crashes
Syndication not working
- Check cron logs:
docker compose logs cron - Ensure syndicator env vars are set in
.env - Syndication runs every 2 minutes — check the last run in logs
- Verify the JWT secret exists:
make shell-cronthencat /data/config/.secret
MongoDB connection errors
- Ensure MongoDB is running:
docker compose ps mongodb - The
MONGODB_URLis set automatically indocker-compose.yml - Check:
docker compose logs mongodb
URL Handling
Posts are served at their canonical Indiekit URLs: /TYPE/YYYY/MM/DD/slug/ (e.g., /notes/2026/02/22/abc123/). Old /content/TYPE/YYYY-MM-DD-slug/ URLs are 301-redirected to the canonical format. The Eleventy data cascade (_data/eleventyComputed.js) auto-converts stale /content/ permalinks in frontmatter.
ActivityPub Federation (Full Profile)
The full profile supports ActivityPub federation, making your site a full AP actor. Other Mastodon/Fediverse users can follow, like, and reply to your posts. Caddy proxies /activitypub* and /nodeinfo/* with CORS headers, and handles AP content negotiation (requests with Accept: application/activity+json or application/ld+json are proxied to Indiekit for AS2 representations).
Configure via .env:
AP_HANDLE— your ActivityPub handle (e.g.,@handle@your-domain.com)AP_LOG_LEVEL— logging level (default:info)AP_DEBUG/AP_DEBUG_PASSWORD— enable debug dashboard
Differences from Cloudron Deployment
Both deployments are at feature parity. They use the same Eleventy theme (Git submodule), the same @rmdes/* plugins, and the same environment-variable-driven configuration.
| Aspect | Cloudron | Docker Compose |
|---|---|---|
| Services | 1 container, 3+ processes | 5-6 containers, 1 process each |
| MongoDB | Cloudron addon | Separate container |
| TLS | Cloudron handles it | Caddy (automatic Let's Encrypt) |
| Config | start.sh orchestration |
Docker entrypoints + compose |
| Background jobs | Shell loops in start.sh | Cron sidecar container |
| File storage | Cloudron /app/data |
Docker named volumes |
| Updates | cloudron build && cloudron update |
docker compose pull && docker compose up -d |
| Plugins | All pre-installed | Core by default, full via override |
| Reverse proxy | nginx | Caddy |
License
MIT