→
Why Miniflux
Deliberately minimalist: no social features, no bloated UI, extremely easy to operate. It reads feeds, stores them in PostgreSQL and exposes a clean API — which makes it ideal as a data source for a threat intelligence pipeline rather than just a reader.
For your pipeline: Miniflux acts as the RSS engine that gathers your security feeds; n8n then pulls new articles via the API for screening/enrichment. Prerequisite: Docker installed.
→
Compose stack
PostgreSQLMiniflux requires PostgreSQL — there is no SQLite option. Stack with an isolated database and admin created on first start.
compose.yaml
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: miniflux
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: miniflux
volumes:
- mf_db:/var/lib/postgresql/data
networks: [ internal ]
healthcheck:
test: [ "CMD", "pg_isready", "-U", "miniflux" ]
interval: 10s
retries: 5
miniflux:
image: miniflux/miniflux:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://miniflux:${DB_PASSWORD}@db/miniflux?sslmode=disable
RUN_MIGRATIONS: 1
CREATE_ADMIN: 1
ADMIN_USERNAME: ${ADMIN_USERNAME}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
BASE_URL: https://rss.defencia.dk/
ports:
- "127.0.0.1:8080:8080"
networks: [ internal, web ]
volumes:
mf_db:
networks:
internal:
internal: true
web:
RUN_MIGRATIONS creates/updates the schema automatically. CREATE_ADMIN creates the admin user on first startup from the env values.
.env
DB_PASSWORD=# openssl rand -base64 24
ADMIN_USERNAME=lars
ADMIN_PASSWORD=# choose a strong password
→
First start
Start the stack
docker compose up -d
docker compose logs -f miniflux
Create an extra admin manually (optional)
docker compose exec miniflux \
miniflux -create-admin
Login: Once the proxy is in place, go to
https://rss.defencia.dk and log in with the admin user. Add feeds one by one or import an entire OPML file under Settings → Import.→
Reverse proxy (Nginx)
/etc/nginx/sites-available/rss.defencia.dk
server {
listen 443 ssl;
server_name rss.defencia.dk;
# ssl_certificate ... (Certbot inserts)
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Miniflux has no websockets — the proxy block is simpler than n8n's. BASE_URL in compose must match this hostname.
Cert:
sudo certbot --nginx -d rss.defencia.dk or your wildcard for *.defencia.dk.→
API for automation
REST + FeverThis is where Miniflux shines as a pipeline component. Generate an API token under Settings → API Keys and call the REST API from n8n.
| Endpoint | Function |
|---|---|
GET /v1/entries?status=unread | Fetch unread articles (for screening in n8n) |
GET /v1/feeds | List all subscribed feeds |
POST /v1/feeds | Add a new feed programmatically |
PUT /v1/entries | Mark articles as read/unread in bulk |
GET /v1/entries?search=TERM | Full-text search across feeds |
GET /v1/me | Verify token / user info |
Example — fetch unread via curl
curl -H "X-Auth-Token: YOUR_TOKEN" \
"https://rss.defencia.dk/v1/entries?status=unread&limit=50"
Pipeline pattern: n8n schedule trigger → call
/v1/entries?status=unread → screen with Mistral → enrich → mark as read via PUT /v1/entries. The token is set as an n8n credential, so it never sits in cleartext in the workflow.→
Operation
Update
docker compose pull
docker compose up -d
Migrations run automatically at startup due to RUN_MIGRATIONS=1.
Backup database
docker compose exec db \
pg_dump -U miniflux miniflux > mf_$(date +%F).sql
Export feeds (OPML)
# in UI: Settings → Export → OPML
# or via API: GET /v1/export
Check status
docker compose ps
docker compose logs --tail=50 miniflux
For your backup stack: Take an OPML export (your ~71 curated feeds, portable) plus a
pg_dump (read/unread states, starred items). OPML alone is enough to rebuild the subscriptions if the database is lost.→
Hardening
| Control | Recommendation |
|---|---|
| Exposure | Bind to 127.0.0.1:8080, only via Nginx + HTTPS |
| Database | Postgres on the internal network, no host port |
| API token | Use a token, not basic auth, for automation; rotate as needed |
| Login | Strong admin password; Fail2ban jail on login |
| Feed proxy | Consider FETCH_* timeouts against slow/malicious feeds |
| Users | Create a separate non-admin user for the API if possible |
| Updates | Keep the image updated — the Go binary is small, upgrades are fast |
Remember the Docker+UFW trap: Bind to
127.0.0.1:8080 — never 0.0.0.0 for internal services. See the Docker guide.