{"id":51018890,"url":"https://github.com/cbrgm/gopodder","last_synced_at":"2026-06-21T14:30:52.721Z","repository":{"id":361195591,"uuid":"1253431646","full_name":"cbrgm/gopodder","owner":"cbrgm","description":"A self-hostable podcast synchronization server compatible with the gPodder API","archived":false,"fork":false,"pushed_at":"2026-06-19T12:24:29.000Z","size":439,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T14:15:42.283Z","etag":null,"topics":["container","go","gopodder","gpodder","gpodder-api","open-source","podcast","podcasts","synchronization"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cbrgm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"ko_fi":"chrisbargmann"}},"created_at":"2026-05-29T13:04:27.000Z","updated_at":"2026-06-19T12:18:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cbrgm/gopodder","commit_stats":null,"previous_names":["cbrgm/gopodder"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/cbrgm/gopodder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrgm%2Fgopodder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrgm%2Fgopodder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrgm%2Fgopodder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrgm%2Fgopodder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cbrgm","download_url":"https://codeload.github.com/cbrgm/gopodder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cbrgm%2Fgopodder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34612996,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["container","go","gopodder","gpodder","gpodder-api","open-source","podcast","podcasts","synchronization"],"created_at":"2026-06-21T14:30:49.951Z","updated_at":"2026-06-21T14:30:52.707Z","avatar_url":"https://github.com/cbrgm.png","language":"Go","funding_links":["https://ko-fi.com/chrisbargmann"],"categories":[],"sub_categories":[],"readme":"# 🎧 goPodder\n\n[![GitHub release](https://img.shields.io/github/release/cbrgm/gopodder.svg)](https://github.com/cbrgm/gopodder/releases/latest)\n[![Go Report Card](https://goreportcard.com/badge/github.com/cbrgm/gopodder)](https://goreportcard.com/report/github.com/cbrgm/gopodder)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/cbrgm/gopodder)](https://github.com/cbrgm/gopodder/blob/main/go.mod)\n[![License](https://img.shields.io/github/license/cbrgm/gopodder)](https://github.com/cbrgm/gopodder/blob/main/LICENSE)\n[![go-lint-test](https://github.com/cbrgm/gopodder/actions/workflows/go-lint-test.yml/badge.svg)](https://github.com/cbrgm/gopodder/actions/workflows/go-lint-test.yml)\n[![container](https://github.com/cbrgm/gopodder/actions/workflows/container.yml/badge.svg)](https://github.com/cbrgm/gopodder/actions/workflows/container.yml)\n\n**A self-hostable podcast synchronization server compatible with the [gPodder API](https://gpoddernet.readthedocs.io/en/latest/api/)**\n\ngoPodder does one thing: it keeps your podcast subscriptions and episode progress in sync across devices and apps. **Just synchronization, done well.** In production since mid-2025, syncing podcasts for friends and family\n\n## Features\n\n- Works with [AntennaPod](https://antennapod.org/), [gPodder](https://gpodder.github.io/), [Cardo](https://github.com/cardo-podcast/cardo), and anything else that speaks the [gPodder sync protocol](https://gpoddernet.readthedocs.io/en/latest/api/)\n- Built-in web UI for managing accounts, users, devices, and subscriptions\n- [REST API](APIDOCS.md) with API key auth for scripts, provisioning, and custom integrations\n- Multi-user support with admin/standard roles, per-account user limits, optional self-registration\n- SQLite by default (zero config), PostgreSQL if you need it\n- Share your subscriptions publicly via OPML and RSS links\n- No outbound connections. The server never phones home, never fetches feed URLs, never resolves external DNS. Your subscription data stays on your box\n- Single binary, no dependencies, ~13 MB RAM at idle. Runs fine on a Raspberry Pi\n\n\u003cp\u003e\n  \u003cimg src=\".github/assets/screenshot-users.jpg\" width=\"49%\" alt=\"Users\"\u003e\n  \u003cimg src=\".github/assets/screenshot-user-detail.jpg\" width=\"49%\" alt=\"User detail\"\u003e\n\u003c/p\u003e\n\n## Why this exists\n\nOther self-hostable options like [opodsync](https://github.com/kd2org/opodsync) require PHP and a web server, while [podsync](https://github.com/bobrippling/podsync) is minimal and lacks a web UI or multi-user support. Full podcast platforms like [Pinepods](https://github.com/madeofpendletonwool/PinePods) or [Audiobookshelf](https://github.com/advplyr/audiobookshelf) are media servers, not sync tools.\n\ngoPodder is a single binary with a built-in web UI, multi-user support, and SQLite or PostgreSQL. You bring your own podcast client (AntennaPod, gPodder, Cardo, etc.) and manage your podcasts there. goPodder only handles the synchronization between them. No runtime dependencies, no over-engineering. It deploys in seconds and just gets the job done. You won't even notice it's running\n\n## Quick start\n\n```bash\ndocker run --rm -p 8080:8080 ghcr.io/cbrgm/gopodder:latest\n```\n\nOpen `http://localhost:8080` in your browser. On first launch you'll be asked to create an admin account. After that, create a goPodder user from the web UI and point your podcast app at the server.\n\n## Setup\n\n### Configuration flags\n\nAll flags can also be set via environment variables. The env var takes precedence over the default but the flag takes precedence over the env var\n\n| Flag | Env var | Default | Description |\n|------|---------|---------|-------------|\n| `--listen-address` | `GOPODDER_LISTEN_ADDRESS` | `0.0.0.0:8080` | HTTP listen address (host:port) |\n| `--debug-address` | `GOPODDER_DEBUG_ADDRESS` | | Debug/metrics listen address (disabled if empty) |\n| `--db-backend` | `GOPODDER_DB_BACKEND` | `sqlite` | Database backend (`sqlite` or `postgres`) |\n| `--db-path` | `GOPODDER_DB_PATH` | `gopodder.db` | Path to SQLite database file |\n| `--db-postgres` | `GOPODDER_DB_POSTGRES` | | PostgreSQL connection string |\n| `--db-postgres-password` | `GOPODDER_DB_POSTGRES_PASSWORD` | | PostgreSQL password (injected into connection string) |\n| `--log-level` | `GOPODDER_LOG_LEVEL` | `info` | Log level (`debug`, `info`, `warn`, `error`) |\n\n### Database backends\n\ngoPodder supports two database backends. Pick whichever fits your setup\n\n**SQLite** (default) is the simplest option. No external database needed. Data is stored in a single file. Good for personal use and small deployments\n\n**PostgreSQL** is available if you already run Postgres or need to scale beyond a single instance\n\n### Docker Compose with SQLite\n\nThe easiest way to run goPodder. Data is persisted in a Docker volume\n\n```yaml\nservices:\n  gopodder:\n    image: ghcr.io/cbrgm/gopodder:latest\n    command: serve --db-path /data/gopodder.db\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - gopodder-data:/data\n    restart: unless-stopped\n\nvolumes:\n  gopodder-data:\n```\n\n### Docker Compose with PostgreSQL\n\n```yaml\nservices:\n  gopodder:\n    image: ghcr.io/cbrgm/gopodder:latest\n    command: serve --db-backend postgres --db-postgres \"postgres://gopodder:secret@db:5432/gopodder?sslmode=disable\"\n    ports:\n      - \"8080:8080\"\n    depends_on:\n      - db\n    restart: unless-stopped\n\n  db:\n    image: postgres:17-alpine\n    environment:\n      POSTGRES_USER: gopodder\n      POSTGRES_PASSWORD: secret\n      POSTGRES_DB: gopodder\n    volumes:\n      - pg-data:/var/lib/postgresql/data\n    restart: unless-stopped\n\nvolumes:\n  pg-data:\n```\n\n### Podman Quadlet with secrets\n\nIf you run Podman and want to avoid putting the database password in plain text, use `GOPODDER_DB_POSTGRES_PASSWORD` with a [Podman secret](https://docs.podman.io/en/latest/markdown/podman-secret-create.1.html):\n\n```ini\n[Container]\nImage=ghcr.io/cbrgm/gopodder:latest\nContainerName=gopodder\nEnvironment=GOPODDER_DB_BACKEND=postgres\nEnvironment=GOPODDER_DB_POSTGRES=postgres://gopodder@pg_server:5432/gopodder\nSecret=gopodder_db_pass,type=env,target=GOPODDER_DB_POSTGRES_PASSWORD\n```\n\nThe password is injected into the connection string at startup. Special characters in the password are URL-encoded automatically\n\n## Connecting your podcast app\n\n1. Create a goPodder user in the web UI (under the \"Users\" tab)\n2. In your podcast app, look for \"gPodder.net sync\" or \"Synchronize subscriptions\"\n3. Set the server URL to `https://your-server`\n4. Log in with the goPodder user credentials you created\n\n**Note:** Some clients (like AntennaPod) require HTTPS. goPodder itself does not terminate TLS, so you'll need to run it behind a reverse proxy like [Caddy](https://caddyserver.com/) or [nginx](https://nginx.org/) that handles HTTPS. For quick testing, tunnels like [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/), [Tailscale Funnel](https://tailscale.com/kb/1223/funnel), or [ngrok](https://ngrok.com/) work but a reverse proxy is the proper solution for permanent setups\n\nTested with:\n\n- [AntennaPod](https://antennapod.org/) (Android)\n- [gPodder](https://gpodder.github.io/) (Desktop, Linux/macOS/Windows)\n- [Cardo](https://github.com/cardo-podcast/cardo) (Windows/macOS/Linux)\n- [KDE Kasts](https://apps.kde.org/kasts/) (Linux/Android/Windows)\n\nAny client that supports the gPodder.net sync protocol should work. If you've tested goPodder with a client not listed here, please [open an issue](https://github.com/cbrgm/gopodder/issues) and let us know\n\n## Backups\n\ngoPodder doesn't need its own backup tooling. Use the standard tools for your database backend\n\n### SQLite\n\nThe database is a single file. goPodder runs in WAL mode, so you can safely copy it while the server is running\n\n```bash\ncp /path/to/gopodder.db /backups/gopodder-$(date +%F).db\n```\n\nWith Docker Compose (assuming you use a bind mount or named volume):\n\n```bash\ndocker cp gopodder:/data/gopodder.db /backups/gopodder-$(date +%F).db\n```\n\n### PostgreSQL\n\nUse `pg_dump` as you would for any Postgres database:\n\n```bash\ndocker exec gopodder-db pg_dump -U gopodder gopodder \u003e /backups/gopodder-$(date +%F).sql\n```\n\nBoth approaches work with any existing backup infrastructure (Proxmox Backup Server, restic, borgmatic, rsync to NAS, etc.). Schedule them via cron or systemd timers\n\n## Monitoring\n\ngoPodder exposes Prometheus metrics and Go pprof profiling on a separate debug server. This is disabled by default and must be explicitly enabled via `--debug-address`\n\n```bash\ngopodder serve --debug-address 127.0.0.1:6060\n```\n\nOnce enabled, the following endpoints are available on the debug address:\n\n| Endpoint | Description |\n|----------|-------------|\n| `/metrics` | Prometheus metrics (HTTP request counts, durations, sync operations) |\n| `/debug/pprof/` | Go pprof profiling (heap, goroutines, CPU) |\n\nThe debug server runs on a separate port from the main application. Keep it on `127.0.0.1` (localhost) for local access only, or bind to `0.0.0.0` if your monitoring stack runs on a different host. In that case, restrict access at the network/firewall level\n\nPrometheus scrape config example:\n\n```yaml\nscrape_configs:\n  - job_name: gopodder\n    static_configs:\n      - targets: ['your-server:6060']\n```\n\n## gPodder API compatibility\n\ngoPodder implements the synchronization parts of the [gPodder API](https://gpoddernet.readthedocs.io/en/latest/api/): authentication, devices, subscriptions, and episode actions. This covers everything podcast clients use to sync data between devices\n\nThe gpodder.net API also includes directory, search, suggestions, and social features. goPodder does not implement these because they are specific to gpodder.net as a platform and no podcast client requires them for synchronization\n\n## How sync works\n\ngoPodder syncs three things: subscriptions (which podcasts you follow), episode actions (played, downloaded, position), and device registrations. All of this is stored per user, not per device. All your devices share the same data.\n\n**Will I lose my podcasts when I add a new device?**\n\nNo. Most apps (AntennaPod, gPodder desktop) pull the server's data first, merge it with whatever you have locally, then push back anything new. You end up with everything from both sides.\n\n**What if I want my phone to overwrite everything?**\n\nSome apps have a \"force upload\" option that replaces the server's subscription list entirely. That will wipe any subscriptions that only existed on your other devices. It's there if you need it, but check your app's sync settings before you hit it. Episode actions are append-only, so those always merge.\n\n**What decides what happens during sync, the server or the app?**\n\nThe app. goPodder just stores what gets sent and serves it back. It doesn't pick a merge strategy, doesn't resolve conflicts, doesn't reorder anything. Subscription adds are added, removes are removed. Episode actions are recorded as-is.\n\n**Does goPodder fetch my feeds or phone home?**\n\nNo. The server never makes outbound network connections. It stores your feed URLs and episode progress as data and hands them back to your devices when they ask. Your podcast app does the actual fetching. goPodder is a sync service, not a feed reader or aggregator.\n\n## REST API\n\ngoPodder provides a REST API for programmatic access to manage users, subscriptions, and accounts. Create API keys in the web UI under **Account \u003e API Keys** and authenticate with a Bearer token:\n\n```bash\ncurl -H \"Authorization: Bearer gp_your_key_here\" https://your-server/api/v1/users\n```\n\nUse it to build backup scripts, provisioning automation, or custom integrations. See the full **[API Documentation](APIDOCS.md)** for all endpoints, examples, and usage guides.\n\n## Architecture\n\n```mermaid\ngraph LR\n    subgraph Clients\n        A1[AntennaPod]\n        A2[gPodder Desktop]\n        A3[Other Apps]\n        Browser[Browser]\n    end\n\n    subgraph goPodder Server\n        subgraph HTTP Server\n            SYNC[gPodder-compatible Sync API]\n            WEB[Web UI + Management API]\n        end\n        STORE[Store]\n    end\n\n    DB[(SQLite / PostgreSQL)]\n\n    A1 \u003c--\u003e|HTTP| SYNC\n    A2 \u003c--\u003e|HTTP| SYNC\n    A3 \u003c--\u003e|HTTP| SYNC\n    Browser \u003c--\u003e|HTTP| WEB\n\n    SYNC \u003c--\u003e STORE\n    WEB \u003c--\u003e STORE\n    STORE \u003c--\u003e DB\n```\n\nPodcast apps sync subscriptions and episode progress over HTTP using the gPodder-compatible sync API. The web UI and its management API handle account, user, and subscription administration. Both talk to the same store layer, which supports SQLite or PostgreSQL\n\n## Building from source\n\nRequirements: Go 1.26+ (all other tools are managed as Go tool dependencies)\n\n```bash\ngo build -o gopodder ./cmd/gopodder\n./gopodder serve\n```\n\n### Code generation\n\nThe project uses [sqlc](https://sqlc.dev/) for type-safe SQL queries and [templ](https://templ.guide/) for HTML templates. Both are declared in `go.mod` as tool dependencies, so no separate installation is needed\n\nAfter modifying SQL queries or `.templ` files, regenerate with:\n\n```bash\ngo generate ./...\n```\n\n### Running tests\n\n```bash\ngo test ./...\n```\n\n### Linting\n\n```bash\ngolangci-lint run ./...\n```\n\n## Contributing\n\nPlease open an issue before submitting large changes\n\n## License\n\nApache 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbrgm%2Fgopodder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcbrgm%2Fgopodder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcbrgm%2Fgopodder/lists"}