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:
demo@omnivore.work - 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.
Frequently Asked Questions
Is Omnivore still maintained after the ElevenLabs acquisition?
The original team was acqui-hired by ElevenLabs in October 2024, and the cloud service shut down. The project is now community-maintained on GitHub under the AGPL-3.0 license. Development continues, but at a slower pace than before. Self-hosting is the only way to use Omnivore going forward.
Why does Omnivore use :latest Docker tags?
Omnivore does not publish versioned Docker image tags — :latest is the only option. This means docker compose pull may change your deployment’s behavior without warning. For reproducible deployments, pin to specific image SHA digests using image: ghcr.io/omnivore-app/sh-backend@sha256:... instead of the tag. Check the GitHub container registry for available digests.
Can I use Omnivore’s mobile apps with my self-hosted instance?
Yes. The native iOS and Android apps support custom server URLs. In the app settings, change the server address to your self-hosted CLIENT_URL. Browser extensions for Chrome, Safari, Firefox, and Edge also support custom server configuration via the GATEWAY_URL.
How much RAM does Omnivore need?
The full 9-container stack uses 2-3 GB of RAM idle and 4 GB under active use. The content-fetch service (headless browser for extracting article content) is the heaviest component. If you’re on a memory-constrained server, consider simpler alternatives like Wallabag (3 containers, ~500 MB) or Linkding (1 container, ~50 MB).
Does Omnivore integrate with Obsidian?
Yes. The official Omnivore Obsidian plugin syncs saved articles, highlights, and annotations directly into your Obsidian vault as Markdown files. It also supports Logseq integration. This makes Omnivore a strong choice for knowledge management workflows where you read-and-annotate articles, then import highlights into your note-taking system.
How do I migrate from Pocket to Omnivore?
Export your Pocket data as an HTML file from Pocket’s export page. Then use Omnivore’s import feature (available in Settings) to upload the HTML file. Article URLs, tags, and read status are imported. The full article content is re-fetched and saved by Omnivore’s content-fetch service, so you get clean copies regardless of Pocket’s export quality.
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