Library / SDK
dan-online/autopulse avatar
dan-online/autopulse

autopulse: telling Plex and Jellyfin about files Sonarr has just finished

💫 automated lightweight service that updates media servers like Plex and Jellyfin based on notifications from media organizers like Sonarr and Radarr

556 stars16 forksRustMIT

At a glance

What is it?
autopulse is a small Rust service that sits between media organisers and media servers, receiving a webhook from Sonarr or Radarr, rewriting the path from where the download landed to where the library expects it, optionally waiting for the file to be complete, and then asking Plex, Jellyfin or Emby to update. The design goal is stated plainly: only update the items that have changed, reducing the load on the media server.
Who is it for?
Adopt autopulse if you run Sonarr or Radarr against Plex, Jellyfin or Emby and want the library to pick up new files promptly without a full rescan, since the service only asks the media server to update what actually changed. Do not expose it to the internet with the default admin and password credentials, because the API can add scan requests, so change them before you do anything else.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem it solves

autopulse is described as an automated, lightweight service that updates media servers like Plex and Jellyfin based on notifications from media organisers like Sonarr and Radarr.

The problem is a small one that anyone running a home media stack will recognise. Sonarr downloads an episode, imports it into a library folder, and then tries to tell Plex that the file is there. Plex has its own library scan, which finds the file eventually, but eventually might be a few minutes or an hour later, and for a series you are watching that delay is visible. So people run a manual scan, or a more aggressive scan schedule, or a second tool, and each of those costs something.

autopulse's approach is a direct webhook from the organiser to a service that knows exactly what changed. The service is small, the request is specific, and the media server is asked to update one item rather than to rescan a library.

The README states the design goal in one sentence: it is designed to be efficient, only updating the items that have changed, reducing the load on media servers. That is the whole thesis, and it explains why the rest of the design is the way it is.

The primary language is Rust, the licence is MIT, the default branch is main, and the homepage is autopulse.dancodes.online, which is the documentation site. The recent releases are v2.0.0 from 2026-06-05, v1.7.0 from 2026-05-25 and v1.6.0 from 2026-03-09, and the last push was on 2026-10-18 with the project not archived. The v2.0.0 release is the most recent versioned release.

Triggers and targets, and the four-step flow

The vocabulary is simple and it is the best thing about the project, because it names the two roles a thing can play.

A trigger is a specification for an endpoint that will be called when a file is ready to be processed. The list is long. Manual, which is the default at /triggers/manual and includes a Fileflows sub-flow the README links to, then Sonarr, Sportarr, Radarr, Lidarr and Readarr, and finally Notify, which is a directory watcher implemented per platform with inotify on Linux, FSEvents on MacOS and ReadDirectoryChangesW on Windows, with polling as the fallback.

A target is a specification for a library that will be updated when a file is ready to be processed. The list covers Plex, Jellyfin, Emby, Command, which appears to run an arbitrary command, Sonarr and Radarr again in the other role, Tdarr, FileFlows, Audiobookshelf and another autopulse instance.

That last entry is worth a moment. autopulse can target another autopulse instance, which means you can chain them, or put a stage in front of a different target. The whole design is a pipeline of pluggable endpoints, and the units compose.

The example flow makes the sequence concrete. Sonarr organises an episode and sends a webhook notification to autopulse. autopulse receives the notification and rewrites the path to the expected path for the target. autopulse optionally checks the file exists and optionally waits for the file to match a provided hash. Then autopulse sends a request to Plex to update or add the episode information and metadata.

Step three is the interesting one, and it addresses a real race. A media organiser can send its webhook before the file is fully written, and if autopulse asks Plex to read a file that is still being copied, Plex sees a truncated or unreadable file. The existence check handles the simplest case and the hash check handles the rest, where the file exists but is not yet the final file.

The six feature bullets summarise the same ground in a different order: rewrites, integration with Sonarr, Radarr, Plex and Jellyfin, checks, reliability through a database that stores the state of scan requests, webhooks that notify you when a file is ready through Discord, Matrix Hookshot or generic JSON, and a simple web interface to view and add scan requests.

Running it with Docker, and the four tags

The getting started section calls the provided Docker image the easiest way in, published on both ghcr.io and Docker Hub, and the tag list is where the design is clearest.

There are four. The latest tag is the full image with support for both postgres and sqlite. The latest-postgres tag is a smaller image that only supports Postgres. The latest-sqlite tag is a smaller image that only supports SQLite. And stable is the latest versioned release.

So the trade is a full image with both database backends, or a smaller one with only the one you use. For a home media server, where people run on a Raspberry Pi or a small NAS, the image size difference between a Rust binary with two database drivers and one with a single driver is not trivial, and the choice of tag is a deliberate part of the installation.

All images are multi-arch and support linux/amd64 and linux/arm64, and the -amd64 and -arm64 suffixes can be used to specify the architecture. That covers both the Intel and Apple Silicon homes, and the Raspberry Pi case is arm64.

The CLI example is short and shows the whole configuration surface for containers:

bash
# create a network
$ docker network create autopulse

# postgres database
$ docker run -d --net autopulse --name postgres -e POSTGRES_PASSWORD=autopulse -e POSTGRES_DB=autopulse postgres
$ docker run -d --net autopulse -e AUTOPULSE__APP__DATABASE_URL=postgres://postgres:autopulse@postgresql/autopulse --name autopulse ghcr.io/dan-online/autopulse

# sqlite database
$ docker run -d --net autopulse -e AUTOPULSE__APP__DATABASE_URL=sqlite://database.db --name autopulse ghcr.io/dan-online/autopulse
# or in-memory
$ docker run -d --net autopulse -e AUTOPULSE__APP__DATABASE_URL=sqlite://:memory: --name autopulse ghcr.io/dan-online/autopulse

The pattern is a Docker network, a Postgres container with a password and a database name, and then autopulse itself with a single environment variable pointing at that database. The SQLite variant swaps the URL for a file path, and the in-memory variant uses sqlite://:memory:, which is the fastest way to try the thing without leaving anything behind.

The environment variable naming is the thing to notice. It is AUTOPULSE__APP__DATABASE_URL, a double underscore marking the section boundary, and the configuration section explains the general form: AUTOPULSE__{SECTION}__{KEY}. So the double underscore marks the separator, and a key like DATABASE_URL becomes DATABASE__URL.

There is a companion convention for secrets, and it is the detail that makes this usable on a real server. You can provide the path to a variable by appending __FILE, so AUTOPULSE__AUTH__PASSWORD__FILE=/run/secrets/autopulse_password reads the password from a file. On a Docker host with secrets mounted, that means the password never appears in the container's environment or in the process list.

For Unraid users there is a template at unraid/autopulse.xml, listed in Unraid Community Apps, and the instructions say to adjust the Media share path and to change AUTOPULSE__AUTH__PASSWORD from the default change-me before clicking Apply. That default matters, and the documentation is explicit about it later on.

Configuration, and the defaults you should change

autopulse requires a configuration file to run. By default it searches the current working directory for config.toml, config.yaml, config.yml or config.json, in that order, and you can pass --config /path/to/config.toml to load an explicit file. So the four formats are tried in a defined sequence, and if you have more than one in the directory the first wins.

The environment variables override the file, which is the standard arrangement and the reason the Docker examples need no config file at all.

The authorisation section is short and the security note is the part to note. autopulse uses basic authorisation for the API, with the username and password set in the config file or through AUTOPULSE__AUTH__USERNAME and AUTOPULSE__AUTH__PASSWORD:

yaml
auth:
  username: terry
  password: yoghurt

By default the username and password are admin and password respectively, and the documentation says in capitals that it is HIGHLY recommended to change these values if you are exposing the API to the internet.

That is worth taking seriously for two reasons. The first is obvious: admin and password over the internet is a bad combination, and a service that triggers scans on your media server is worth protecting. The second is that the credentials are not the only thing exposed. The API can add scan requests, so anyone who reaches it can make your server do work, and if the service is on a network with the media server on it, the effect is to trigger library activity on demand.

The trigger configuration is a YAML structure that reads like a routing table, with a name, a type, a rewrite with a from and a to, and a filter:

yaml
triggers:
  my_sonarr:
    type: "sonarr"
    rewrite:
      from: "/downloads"
      to: "/tvshows"
    filter:
      exclude:
        - "^/tvshows/extras/"

The rewrite is the path translation from the download folder to the library folder, which is the core of what autopulse does. The filter with an exclude list takes regular expressions, and the example excludes a path under the library directory, which is the sensible pattern: a specials or extras folder is usually not something you want Plex to show as an episode.

The same shape appears for each trigger type, so adding a Radarr trigger is a copy of the Sonarr one with a different type and different paths.

The documentation site carries the rest of the reference, with separate pages for settings, targets, triggers and webhooks, and the repository ships an example directory with config.yaml and two Docker Compose files, one for SQLite and one for Postgres.

The codebase, and what the crate layout reveals

The repository listing shows a Rust project with a workspace, a database layer, a service layer and a server layer, and the Cargo manifest makes the feature structure explicit.

There are three top-level feature flags, and each one turns on a database backend across several crates. The sqlite feature enables autopulse-database/sqlite, autopulse-server/sqlite and autopulse-service/sqlite. The postgres feature enables the same three with postgres. And there is a vendored feature, which enables autopulse-service/vendored, postgres-vendored and sqlite-vendored.

That vendored feature is the interesting one, and it explains the size difference between the image tags. A vendored build statically links the database driver rather than depending on a system library, so the image carries the driver itself. The size difference between the full image and the single-backend images comes from the same mechanism, and the tag list is a direct consequence of how the features compose.

The dependencies include diesel, which is the Rust ORM, and there is a diesel.toml at the root for its configuration. The crate names also reveal the split of responsibilities: autopulse-database handles persistence, autopulse-service holds the trigger and target logic, and autopulse-server is the web layer. The workspace version is 2.0.0 and the package version comes from the workspace, so there is one version number for the whole project.

The tooling is a mix that shows a developer who cares about both reproducibility and ergonomics. There is a rust-toolchain.toml pinning the compiler, a flake.nix and flake.lock for Nix, a lefthook.yml for git hooks, and a cliff.toml for changelog generation. There are three Dockerfiles, a plain one, a dev one and a src one, and a docker-entrypoint.sh.

The assets/ directory holds the logo, and the documentation site lives at autopulse.dancodes.online with a separate front end at autopulseui.pages.dev.

The use of SQLite in the default configuration is worth noting alongside all this. For a service that stores the state of scan requests, that state is small and short-lived, and SQLite removes a database from the install entirely. The Postgres path exists for people who run several instances or want the state to survive a specific container lifecycle.

How to evaluate it, and the questions to ask first

There are four things to decide.

First, whether you have the problem. autopulse is for people running Sonarr or Radarr against Plex, Jellyfin or Emby and who want the library to update promptly without a full rescan. If you are not running that stack, there is nothing here for you. If you are running it and have not noticed a problem, the benefit is small and the service is one more thing to keep running.

Second, the security posture, and this is the one to settle first. The default credentials are admin and password, the API uses basic authorisation, and the API can add scan requests. The documentation is emphatic about changing the defaults if the API is exposed to the internet, and the __FILE convention for secrets gives you a way to keep the password out of the environment. Whether the service should be exposed at all is your decision, and the web interface means there is something to protect.

Third, the database choice. The SQLite in-memory option is there for a quick trial. The file-based SQLite option needs a volume, so the container needs one. Postgres needs a second container. The unraid template assumes a particular path layout, with a Media share to adjust. None of this is difficult, but it is a decision you make once at install time.

Fourth, the trigger and target set. The list is long enough that most of what you need is there, including the Notify watcher with per-platform implementations and a polling fallback, which is what you would use if you are not running an organiser at all. The Command target is the one to look at if you need something the built-in targets do not cover, since it appears to let you run an arbitrary command instead.

What you get in exchange is a small, single-purpose service that does one thing quickly and gets out of the way. The latest versioned release is 2.0.0, so it is worth reading the settings documentation rather than copying an old config blindly. And the documentation is a real website with a page per concept, which is what makes a project with this many endpoints usable at all.

Editorial conclusion

Adopt autopulse if you run Sonarr or Radarr against Plex, Jellyfin or Emby and want the library to pick up new files promptly without a full rescan, since the service only asks the media server to update what actually changed. Do not expose it to the internet with the default admin and password credentials, because the API can add scan requests, so change them before you do anything else. Before you commit, decide between the single-database image tags and the full one, since the vendored build features that make the smaller images possible are the reason the size differs, set up the password through the __FILE convention so it stays out of the environment, and read the settings documentation rather than copying an old config blindly.

Frequently asked questions

What is autopulse?

It is a Rust web server that receives notifications from media organisers such as Sonarr, Radarr and Lidarr and updates the items in media servers such as Plex, Jellyfin and Emby. It is designed to be efficient, only updating the items that have changed, which reduces the load on the media server. It is MIT licensed and documented at autopulse.dancodes.online.

What are triggers and targets in autopulse?

A trigger is a specification for an endpoint called when a file is ready to be processed, and includes Manual, Sonarr, Sportarr, Radarr, Lidarr, Readarr, Fileflows and Notify, which watches a directory with inotify on Linux, FSEvents on MacOS and ReadDirectoryChangesW on Windows, falling back to polling. A target is a library updated when a file is ready, and includes Plex, Jellyfin, Emby, Command, Sonarr, Radarr, Tdarr, FileFlows, Audiobookshelf and another autopulse instance.

How do I run autopulse with Docker?

Pull the image from ghcr.io or Docker Hub and run it with a database URL environment variable. The images come in four tags: latest with both postgres and sqlite support, latest-postgres and latest-sqlite as smaller single-backend builds, and stable for the latest versioned release. All are multi-arch for linux/amd64 and linux/arm64, and you can use a SQLite file or sqlite://:memory: for a quick trial.

How is autopulse configured?

With a config file searched for in the current working directory as config.toml, config.yaml, config.yml or config.json in that order, or an explicit path passed with --config. Environment variables in the form AUTOPULSE__{SECTION}__{KEY} override the file, and appending __FILE to a variable makes it read the value from a file, for example AUTOPULSE__AUTH__PASSWORD__FILE=/run/secrets/autopulse_password.

What are the default autopulse credentials and should I change them?

The API uses basic authorisation and the defaults are admin and password. The documentation says it is HIGHLY recommended to change these values if you are exposing the API to the internet, and the Unraid setup instructions also say to change AUTOPULSE__AUTH__PASSWORD from the default change-me before applying the template.

Official sources

  1. dan-online/autopulse on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dan-online-autopulse.svg)](https://hysenlabs.com/projects/dan-online-autopulse)

Was this useful?