Production-ready Docker Compose deployment for FeedLand https://rmendes.net/content/articles/2026-02-17-how-to-self-host-feedland/
  • Shell 97.5%
  • HTML 2.5%
Find a file
2026-02-17 18:17:29 +01:00
db Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
scripts Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
.dockerignore Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
.env.example Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
.gitignore Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
Caddyfile Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
CLAUDE.md Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
docker-compose.yml Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
emailtemplate.html Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
entrypoint.sh Production-ready FeedLand Docker Compose deployment 2026-02-17 18:10:37 +01:00
README.md Add context section to README crediting original repo and Dave Winer 2026-02-17 18:17:29 +01:00

FeedLand Docker

Production-ready Docker Compose deployment for FeedLand by Dave Winer.

Four containers: FeedLand (Node.js app), MySQL 8.0 (database), Caddy (reverse proxy with automatic HTTPS), Mailpit (built-in mail catcher).

Why This Repo

This project builds on scotthansonde/feedlandDockerCompose, the original Docker Compose setup for FeedLand. The original repo uses two Alpine init containers that install packages and download the database schema from GitHub on every startup, exposes FeedLand ports 1452/1462 and MySQL 3306 directly to the host bypassing TLS, ships dummy SMTP credentials that silently fail, has no healthcheck on FeedLand, no network segmentation, a hardcoded timezone, and makes Caddy an optional profile requiring manual activation. This repo eliminates all init containers with a single entrypoint script, vendors the schema so there's no runtime GitHub dependency, enforces network segmentation so only Caddy exposes ports 80/443, includes a built-in Mailpit mail catcher at /mail so email confirmation works out of the box without configuring an external SMTP provider, adds healthchecks on every service, and ships backup/migrate scripts — turning a fragile developer setup into a production-ready single-command deployment.

Prerequisites

  • Docker and Docker Compose (v2)
  • A domain name with DNS pointing to your server
  • Ports 80 and 443 open

Quick Start

# 1. Generate .env (prompts for domain, generates MySQL passwords)
./scripts/generate-env.sh

# 2. Start everything
docker compose up -d

All four services will start. Caddy automatically obtains a TLS certificate from Let's Encrypt. Emails are caught by the built-in Mailpit and viewable at https://your-domain.com/mail.

Configuration

All settings are controlled via .env. See .env.example for the full list.

Required

Variable Description
FEEDLAND_DOMAIN Your domain name (must resolve to this server)
MYSQL_ROOT_PASSWORD MySQL root password
MYSQL_PASSWORD MySQL password for the feedland user

SMTP

By default, emails go to the built-in Mailpit mail catcher. This works well for single-user / self-hosted setups — confirmation emails are viewable at https://your-domain.com/mail.

For multi-user setups where emails need to reach real inboxes, configure a real SMTP server in .env:

Variable Default Description
SMTP_HOST mailpit SMTP server hostname
SMTP_PORT 1025 SMTP port
SMTP_USERNAME (empty) SMTP username
SMTP_PASSWORD (empty) SMTP password
MAIL_SENDER feedland@localhost "From" address for emails

Example for a real SMTP provider:

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=you@gmail.com
SMTP_PASSWORD=your_app_password
MAIL_SENDER=noreply@your-domain.com

Optional

Variable Default Description
MYSQL_DATABASE feedland MySQL database name
MYSQL_USER feedland MySQL username
FEEDLAND_PRODUCT_NAME FeedLand Product name in UI and emails
ENABLE_NEW_USERS true Allow new user registrations
TZ UTC Timezone (IANA format)

Architecture

Internet → Caddy (ports 80/443, automatic HTTPS)
              ├─ /mail  → Mailpit (email web UI)
              ├─ ws://  → FeedLand :1462 (WebSocket)
              └─ /*     → FeedLand :1452 (HTTP)
                            ↓ (backend network)
                         MySQL 8.0
  • Only Caddy is accessible from the internet
  • FeedLand, MySQL, and Mailpit are on internal Docker networks only
  • Caddy cannot reach MySQL (network segmentation)

Creating Your First User

  1. Open https://your-domain.com in a browser
  2. Click "Sign up" and enter a username and email
  3. Go to https://your-domain.com/mail to find the confirmation email
  4. Click the confirmation link

If you've configured real SMTP, the email arrives in the user's real inbox instead.

Backup and Restore

Backup

# Create a compressed backup
./scripts/backup.sh

# Backups are stored in ./backups/
# Last 7 are retained by default (set RETAIN=N to change)

Restore

# Restore from a backup
gunzip < backups/feedland-2026-02-17_120000.sql.gz | \
  docker compose exec -T mysql \
  mysql -uroot -p"$MYSQL_ROOT_PASSWORD" feedland

Scheduled Backups

Add to crontab (crontab -e):

0 3 * * * cd /path/to/feedland-docker && ./scripts/backup.sh >> backups/cron.log 2>&1

Updating FeedLand

# Pull the latest image
docker compose pull feedland

# Restart with the new image
docker compose up -d

If the update includes database schema changes, check the FeedLand worknotes and apply migrations:

# Save the migration SQL to a file, then:
./scripts/migrate.sh path/to/migration.sql

Common Commands

# View logs (all services)
docker compose logs -f

# View logs (one service)
docker compose logs -f feedland

# Stop all services
docker compose down

# Restart a single service
docker compose restart feedland

# Check service health
docker compose ps

# Open a MySQL shell
docker compose exec mysql mysql -ufeedland -p feedland

Troubleshooting

Services won't start / "Set FEEDLAND_DOMAIN in .env" Ensure .env exists with all required variables set. Run ./scripts/generate-env.sh if you haven't.

Caddy fails to get a certificate

  • Verify your domain's DNS A/AAAA record points to this server
  • Ensure ports 80 and 443 are open and not used by another service
  • Check Caddy logs: docker compose logs caddy
  • For testing, uncomment the staging CA line in Caddyfile to avoid rate limits

FeedLand healthcheck failing

  • Check FeedLand logs: docker compose logs feedland
  • Verify MySQL is healthy: docker compose ps mysql
  • Check the generated config: docker compose exec feedland cat /project/config.json

MySQL won't start

  • Check logs: docker compose logs mysql
  • If schema was corrupted, you may need to remove the volume: docker compose down -v (this deletes all data)

Email confirmation not working

  • Default setup: Check https://your-domain.com/mail for caught emails
  • Real SMTP: Verify SMTP settings in .env, check docker compose logs feedland for errors