Docker Compose + Ansible deployment for Indiekit, an IndieWeb server with Micropub support, static site generation, and POSSE syndication.
Internet
|
┌──────┴──────┐
│ Caddy :443 │ Automatic HTTPS (Let's Encrypt)
│ :80 → :443 │ Static site + reverse proxy
└──┬───────┬──┘
│ │
┌───────────┘ └──────────┐
▼ ▼
┌────────────────┐ ┌─────────────────┐
│ Indiekit:8080 │ │ Eleventy │
│ Micropub │──content──▶│ Watch + rebuild │
│ Auth, Admin │ volume │ Static HTML │
└───────┬────────┘ └─────────────────┘
│ │
┌───────┴────────┐ site volume → Caddy serves
│ MongoDB │
│ Data store │ ┌─────────────────┐
└────────────────┘ │ Cron sidecar │
│ Syndication 2m │
│ Webmentions 5m │
└─────────────────┘
Services: MongoDB, Indiekit (Node.js), Eleventy (static site builder), Caddy (HTTPS reverse proxy), Cron (background jobs). Optional Redis cache.
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.
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/loginCaddy automatically provisions a Let's Encrypt TLS certificate for your domain. Make sure your DNS A record points to your server before starting.
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) |
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 neededFor 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 -dImages 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.25If you prefer to build locally instead, use make build or make build-full.
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
Minimal plugin set for a functional IndieWeb blog:
| Category | Plugins |
|---|---|
| Post types | article, bookmark, like, note, photo, reply, repost |
| Preset | @rmdes/indiekit-preset-eleventy (permalink fix) |
| Store | @indiekit/store-file-system |
| Endpoints | Micropub, Syndicate, JSON Feed, Webmention.io, Webmention Sender |
| Syndicators | Mastodon, Bluesky, LinkedIn, IndieNews |
make upAll @rmdes plugins — adds GitHub activity, Funkwhale, Last.fm, YouTube, RSS reader, Microsub, Webmentions proxy, Podroll, extra post types (audio, event, jam, rsvp, video, page).
make up-fullAll configuration is done through the .env file. See .env.example for the full reference with documentation.
| 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 |
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
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
For automated provisioning on a fresh server.
- 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
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.ymlansible-playbook -i inventory playbook.yml --tags updatemake 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 themeThe 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 restartCaddy 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.
make backup
# Creates backups/indiekit-YYYYMMDD-HHMMSS.tar.gz containing:
# content/ — all your posts
# uploads/ — media files
# mongodb/ — database
# config/ — config + JWT secretmake restore FILE=backups/indiekit-20260207-120000.tar.gz
# Stops services, restores volumes, restartsdocker compose pull # Pull latest images
docker compose up -d # Restart with new imagesgit pull
make update-theme # if theme has updates
make build # rebuild images
make up # restart with new images- 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 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
- 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
- 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
- Ensure MongoDB is running:
docker compose ps mongodb - The
MONGODB_URLis set automatically indocker-compose.yml - Check:
docker compose logs mongodb
| 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 |
MIT