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:

SubdomainTargetPort
omnivore.yourdomain.comweb3000
api.omnivore.yourdomain.combackend4000
images.omnivore.yourdomain.comimage-proxy7070

See our Reverse Proxy Setup guide for full Nginx Proxy Manager or Caddy configuration.

Backup

Back up three volumes:

VolumeContains
omnivore-postgresAll articles, highlights, annotations, user data
omnivore-redisQueue state (can be regenerated)
omnivore-minioUploaded 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.

Comments