- Shell 97.5%
- HTML 2.5%
| db | ||
| scripts | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| Caddyfile | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| emailtemplate.html | ||
| entrypoint.sh | ||
| README.md | ||
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
- Open
https://your-domain.comin a browser - Click "Sign up" and enter a username and email
- Go to
https://your-domain.com/mailto find the confirmation email - 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
Caddyfileto 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/mailfor caught emails - Real SMTP: Verify SMTP settings in
.env, checkdocker compose logs feedlandfor errors