How to Self-Host Omnivore with Docker Compose
What Is Omnivore?
Omnivore is a self-hosted read-later application originally launched as a hosted service with over 500,000 users. After the team was acqui-hired by ElevenLabs in October 2024, the cloud service shut down — making self-hosting the only way to use Omnivore. It saves articles, PDFs, and newsletters with highlights, annotations, labels, and full-text search. Native iOS and Android apps, browser extensions for Chrome/Safari/Firefox/Edge, and integrations with Obsidian and Logseq make it a full-featured Pocket replacement. The project is open source (AGPL-3.0) and maintained by the community.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 4 GB of free RAM (10+ container stack)
- 10 GB of free disk space
- A domain name (recommended for browser extension compatibility)
Docker Compose Configuration
Omnivore requires a multi-container stack: web frontend, backend API, queue processor, content fetcher, image proxy, database migration service, PostgreSQL with pgvector, Redis, and MinIO for object storage.
Create a docker-compose.yml file:
services:
postgres:
image: ankane/pgvector:v0.5.1
container_name: omnivore-postgres
restart: unless-stopped
environment:
POSTGRES_USER: omnivore
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: omnivore
volumes:
- omnivore-postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U omnivore"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7.2.4
container_name: omnivore-redis
restart: unless-stopped
volumes:
- omnivore-redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
minio:
image: minio/minio:RELEASE.2024-01-18T22-51-28Z
container_name: omnivore-minio
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
command: server /data --console-address ":9001"
volumes:
- omnivore-minio:/data
migrate:
image: ghcr.io/omnivore-app/sh-migrate:latest # Omnivore does not publish versioned Docker tags — :latest is the only option
container_name: omnivore-migrate
environment:
PG_HOST: postgres
PG_USER: omnivore
PG_PASSWORD: ${POSTGRES_PASSWORD}
PG_DB: omnivore
depends_on:
postgres:
condition: service_healthy
restart: "no"
backend:
image: ghcr.io/omnivore-app/sh-backend:latest # Omnivore does not publish versioned Docker tags — :latest is the only option
container_name: omnivore-backend
restart: unless-stopped
ports:
- "4000:4000"
environment:
API_ENV: production
PG_HOST: postgres
PG_USER: omnivore
PG_PASSWORD: ${POSTGRES_PASSWORD}
PG_DB: omnivore
PG_PORT: "5432"
JWT_SECRET: ${JWT_SECRET}
SSO_JWT_SECRET: ${SSO_JWT_SECRET}
CLIENT_URL: ${CLIENT_URL}
GATEWAY_URL: ${GATEWAY_URL}
CONTENT_FETCH_URL: http://content-fetch:9090/
REDIS_URL: redis://redis:6379
IMAGE_PROXY_URL: ${IMAGE_PROXY_URL}
MINIO_URL: http://minio:9000
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
MINIO_UPLOAD_BUCKET: omnivore-uploads
depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
queue-processor:
image: ghcr.io/omnivore-app/sh-queue-processor:latest # Omnivore does not publish versioned Docker tags — :latest is the only option
container_name: omnivore-queue
restart: unless-stopped
environment:
PG_HOST: postgres
PG_USER: omnivore
PG_PASSWORD: ${POSTGRES_PASSWORD}
PG_DB: omnivore
PG_PORT: "5432"
REDIS_URL: redis://redis:6379
JWT_SECRET: ${JWT_SECRET}
CONTENT_FETCH_URL: http://content-fetch:9090/
depends_on:
migrate:
condition: service_completed_successfully
redis:
condition: service_healthy
content-fetch:
image: ghcr.io/omnivore-app/sh-content-fetch:latest # Omnivore does not publish versioned Docker tags — :latest is the only option
container_name: omnivore-content-fetch
restart: unless-stopped
environment:
REST_BACKEND_ENDPOINT: http://backend:4000/api
depends_on:
- backend
image-proxy:
image: ghcr.io/omnivore-app/sh-image-proxy:latest # Omnivore does not publish versioned Docker tags — :latest is the only option
container_name: omnivore-image-proxy
restart: unless-stopped
ports:
- "7070:7070"
environment:
ALLOWED_ORIGINS: "*"
web:
image: ghcr.io/omnivore-app/sh-web:latest # Omnivore does not publish versioned Docker tags — :latest is the only option
container_name: omnivore-web
restart: unless-stopped
ports:
- "3000:3000"
environment:
NEXT_PUBLIC_APP_ENV: production
NEXT_PUBLIC_BASE_URL: ${CLIENT_URL}
NEXT_PUBLIC_SERVER_BASE_URL: ${GATEWAY_URL}
NEXT_PUBLIC_HIGHLIGHTS_BASE_URL: ${CLIENT_URL}
depends_on:
- backend
volumes:
omnivore-postgres:
omnivore-redis:
omnivore-minio:
Create a .env file alongside it:
# Database — change POSTGRES_PASSWORD to a strong random value
POSTGRES_PASSWORD=change-me-to-a-strong-password
# JWT secrets — generate with: openssl rand -hex 32
JWT_SECRET=change-me-generate-with-openssl-rand-hex-32
SSO_JWT_SECRET=change-me-generate-another-with-openssl-rand-hex-32
# MinIO (S3-compatible object storage) — change these
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=change-me-to-a-strong-minio-password
# URLs — set to your domain
CLIENT_URL=http://localhost:3000
GATEWAY_URL=http://localhost:4000
IMAGE_PROXY_URL=http://localhost:7070
Start the stack:
docker compose up -d
Initial Setup
After starting the stack, Omnivore creates a demo account automatically:
- URL:
http://your-server:3000 - Email:
[email protected] - Password:
demo_password
Log in with the demo credentials, then immediately change the password or create a new account. The demo account is useful for verifying the deployment works before configuring your own account.
To create additional users, use the backend API or configure email-based registration by setting SENDER_EMAIL and SMTP environment variables in the backend service.
Configuration
Public URL Setup
For browser extensions and mobile apps to work, the CLIENT_URL and GATEWAY_URL must be publicly accessible. Update your .env:
CLIENT_URL=https://omnivore.yourdomain.com
GATEWAY_URL=https://api.omnivore.yourdomain.com
IMAGE_PROXY_URL=https://images.omnivore.yourdomain.com
Browser Extensions
Install the Omnivore browser extension for Chrome, Firefox, Safari, or Edge. Point the extension at your self-hosted instance by entering your GATEWAY_URL in the extension settings.
Newsletter Ingestion
Omnivore can receive newsletters via email. To enable this, deploy the sh-local-mail-watcher container and configure an MX record pointing to your server. Newsletters sent to your Omnivore email address are automatically saved as articles.
API Access
Omnivore exposes a GraphQL API at http://your-server:4000/api/graphql. Generate API keys from the web UI under Settings → API Keys. The API enables custom integrations and automation workflows.
Reverse Proxy
You need three reverse proxy entries — one for the web UI, one for the API, and one for the image proxy:
| Subdomain | Target | Port |
|---|---|---|
omnivore.yourdomain.com | web | 3000 |
api.omnivore.yourdomain.com | backend | 4000 |
images.omnivore.yourdomain.com | image-proxy | 7070 |
See our Reverse Proxy Setup guide for full Nginx Proxy Manager or Caddy configuration.
Backup
Back up three volumes:
| Volume | Contains |
|---|---|
omnivore-postgres | All articles, highlights, annotations, user data |
omnivore-redis | Queue state (can be regenerated) |
omnivore-minio | Uploaded files and PDFs |
The PostgreSQL volume is critical. Back it up with pg_dump:
docker exec omnivore-postgres pg_dump -U omnivore omnivore > omnivore-backup.sql
See our Backup Strategy guide for automated backup approaches.
Troubleshooting
Migration Container Keeps Restarting
Symptom: The omnivore-migrate container fails and other services won’t start.
Fix: Check PostgreSQL is healthy first: docker logs omnivore-postgres. Ensure the database credentials in .env match between the postgres and migrate services. The pgvector extension must be available — the ankane/pgvector image includes it by default.
Browser Extension Won’t Connect
Symptom: The browser extension shows a connection error.
Fix: The GATEWAY_URL must be publicly accessible with HTTPS. Self-signed certificates cause issues — use Let’s Encrypt via your reverse proxy. Verify the API is reachable: curl https://api.omnivore.yourdomain.com/api/graphql.
Articles Not Fetching Content
Symptom: Saved articles show only the URL, not the article content.
Fix: Check the content-fetch container logs: docker logs omnivore-content-fetch. This service uses a headless browser to extract article content. It needs sufficient memory (at least 512 MB for this container alone). Ensure the REST_BACKEND_ENDPOINT points to the correct backend URL.
High Memory Usage
Symptom: The stack uses 3-4 GB of RAM.
Fix: This is expected. The 9-container stack includes PostgreSQL, Redis, MinIO, a Node.js backend, a Next.js frontend, and a headless browser for content fetching. Minimum 4 GB RAM recommended. On memory-constrained systems, consider Wallabag or Linkding instead.
Resource Requirements
- RAM: ~2-3 GB idle across all containers, 4 GB under active use
- CPU: Medium (content fetching uses headless browser)
- Disk: ~500 MB for application data, plus storage for saved articles and PDFs
Verdict
Omnivore is the most feature-rich self-hosted read-later app available — highlights, annotations, labels, full-text search, newsletter ingestion, native mobile apps, and integrations with Obsidian and Logseq. The trade-off is complexity: 9+ containers, no pinned version tags (all images use :latest), and a community-maintained project after the original team joined ElevenLabs. If you want a full Pocket replacement with every feature, Omnivore delivers. If you want something simpler to deploy and maintain, Wallabag (3 containers) or Linkding (1 container) are better choices. For a modern, AI-powered alternative, Hoarder offers automatic tagging with a simpler stack.
Note: The Omnivore Docker images use :latest tags with no pinned versions. This means your deployment may change behavior after pulling new images. Pin to specific SHA digests if you need reproducible deployments.
Related
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments