Installation

Docker + Docker Compose · runs on any Linux, macOS, or Windows host

Requirements

Quick start

The fastest path to a running instance:

bash
# Download the compose file
curl -O https://raw.githubusercontent.com/CastCharm/castcharm/main/docker-compose.yml

# Start in the background
docker compose up -d

Then open http://localhost:8000 in your browser. A setup wizard will run on the first visit.

Default paths: the database is saved to ./data/ and downloaded audio files go into ./downloads/, both relative to the directory containing your docker-compose.yml.

First run

The wizard walks you through six short steps. You can change any of these later in Settings.

  1. Login — pick a username and password, or skip this step to leave your instance open.
  2. Theme — pick a colour scheme.
  3. Timezone — used for date-prefixed filenames and year-based folders.
  4. Downloads folder — where inside the container to save audio files. This maps to DOWNLOAD_PATH on the host.
  5. File options — filename format and folder organisation.
  6. External API — leave on if you plan to use the Android app or scripts; leave off if you'll only ever use the web interface.

Full docker-compose.yml

Below is the complete file for reference. You can customise paths and the port via environment variables or a .env file placed in the same directory.

docker-compose.yml
services:
  castcharm:
    image: ghcr.io/castcharm/castcharm:latest
    container_name: castcharm
    ports:
      - "${PORT:-8000}:8000"
    volumes:
      # database and app state
      - ${DATA_PATH:-./data}:/data
      # downloaded audio files
      - ${DOWNLOAD_PATH:-./downloads}:/downloads
    environment:
      - DATABASE_URL=sqlite:////data/castcharm.db
      - DEFAULT_DOWNLOAD_PATH=/downloads
      - CLEAN_RSS_PATH=/downloads/clean-rss
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/api/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 20s

Environment variables

Set these in a .env file in the same folder as your docker-compose.yml, or pass them directly to the container.

Most people only need these three:

Variable Default Description
PORT 8000 Host port the app is exposed on.
DATA_PATH ./data Host path for the SQLite database and application state. Back this up to preserve all feeds, settings, and playback history.
DOWNLOAD_PATH ./downloads Host path where audio files are saved. Point this at an existing media directory if you want CastCharm to manage files you already have.

Advanced options

You can safely skip this section unless you're running CastCharm behind a reverse proxy in another container, or building your own image.

Variable Default Description
CASTCHARM_TRUSTED_PROXIES (empty) Comma-separated list of IP addresses that are allowed to tell CastCharm the "real" client IP via the X-Forwarded-For header. The loopback address (127.0.0.1) is trusted by default, so a proxy on the same host needs no change. If your proxy runs in its own container (Traefik, nginx in Docker), add its container IP here — otherwise the login rate-limit will lump every failed attempt together under the proxy's address.
APP_VERSION dev Version string reported by /api/status and shown in the API docs. Set automatically by official container images; only useful when building your own.

The login cookie's Secure flag is set automatically based on whether the current request came in over HTTPS, so no configuration is needed for either plain-HTTP or HTTPS deployments.

The DATABASE_URL, DEFAULT_DOWNLOAD_PATH, and CLEAN_RSS_PATH variables inside the container are set automatically by the compose file and do not normally need to be changed.

Running behind a reverse proxy

CastCharm works behind nginx, Caddy, Traefik, or any other reverse proxy. If your proxy runs on the same host as CastCharm (the usual setup), no configuration is needed — just point it at the container and go.

If your proxy runs in a separate container and you want the login rate-limit to see the real client IP instead of the proxy's, set CASTCHARM_TRUSTED_PROXIES to the proxy's container IP. See the Advanced options above.

Caddy example

Caddyfile
podcasts.example.com {
    reverse_proxy localhost:8000
}

nginx example

nginx.conf
server {
    listen 443 ssl;
    server_name podcasts.example.com;

    location / {
        proxy_pass         http://localhost:8000;
        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;
    }
}

External API access

Everything the web interface can do is also available as a REST API. This is what the Android app uses to talk to your server, and it's how scripts and home-automation tools can reach your library.

To let an outside app or script in:

  1. Open Settings → External API in the web interface.
  2. Make sure Enable external API access is on.
  3. Click Generate new key and give it a name (e.g. "Phone" or "Backup script").
  4. Copy the key that appears — it's only shown once.

The Android app handles all of this for you: log in once with your username and password, and the app generates and stores its own key automatically.

Managing keys

The keys list in Settings shows when each key was last used. You can:

CastCharm also runs a small daily cleanup that removes keys never used within a week of being created — usually the fingerprint of a failed enrolment.

Interactive API docs

Full API reference with an interactive "try it out" panel is at /api/docs on your instance (e.g. http://localhost:8000/api/docs). Use the Authorize button to paste an API key for testing.

Playlists

There are two kinds, chosen when you create one and fixed from then on. A manual playlist holds the episodes you add to it, in the order you drag them into. A smart playlist (the brain icon) holds whatever matches its rules right now, so it keeps itself current as episodes arrive and get played.

Smart playlist rules, all optional and combined with "and":

Only episodes downloaded on the server are ever included. While you edit the rules, the editor shows how many episodes they match and how long that is in total. Playing a smart playlist, from its Play button or any row, works through it in order and carries on to the next episode by itself; the Android app does the same, offline too.

Get Caught Up is a smart playlist that comes with CastCharm: unplayed episodes from the last two weeks, oldest first. It sits at the top of the Home page and first in Android Auto. Change its rules to taste; it cannot be deleted.

Notifications

CastCharm can send you a message when it finds new episodes: one message per sync, listing the podcasts and episodes. There's nothing to install on the server. Under Settings → Notifications, choose how to send it and paste one address.

webhook body
{
  "event": "new_episodes",
  "title": "2 new episodes",
  "message": "Podcast — Episode title\nOther podcast — Another episode",
  "count": 2,
  "link": "https://podcasts.example.com",
  "feeds": [{ "id": 7, "title": "Podcast", "episodes": [{ "id": 912, "title": "Episode title", "published_at": "2026-10-09T06:00:00" }] }],
  "sent_at": "2026-10-09T06:05:12Z"
}

Fill in Public URL with the address you open CastCharm at and the message will link straight to the podcast. Use Send test message after saving to make sure it arrives.

Notifications are only available once login is turned on (Settings → Security). Sending a message means the server makes a request to an address of your choosing; on an open instance anyone on the network could change that address, so the setting is reserved for the signed-in owner, can't be changed with an API key, and the server never follows redirects or shows the reply it gets back.

Data & backups

To restore: stop the container, replace the files, start again.

Build from source

If you prefer to build the image locally rather than pulling from the registry:

bash
git clone https://github.com/CastCharm/castcharm
cd castcharm
docker compose up -d --build

Updating

bash
# Pull the latest image and restart
docker compose pull
docker compose up -d

Database migrations run automatically on startup. No manual steps are required when upgrading.