Docker
Already comfortable with Docker? Skip ahead to Installing Profilarr.
Docker runs software in containers. A few terms come up throughout this page:
- Image: a packaged snapshot of an app and everything it needs to run. Profilarr’s image includes Profilarr itself, plus Git for pulling databases and the SQLite library it keeps its settings in, so none of that has to be installed on your machine. Images are published to a registry and pulled by name: Profilarr’s is
ghcr.io/dictionarry-hub/profilarr:tagon GitHub’s registry, where the tag after the colon chooses a release channel. - Container: an isolated environment started from an image. Containers are ephemeral: they’re meant to be replaced, not kept around, so updating means swapping the container for a new one started from a newer image.
- Volume: a folder on your machine that’s mounted into the container and survives that replacement. Profilarr keeps everything worth keeping in one volume,
/config.
Docker is the recommended way to install Profilarr: the same image runs the same way everywhere, and updates never touch your data.
New to Docker? Docker’s own guides go deeper on each of these: What is a container?, What is an image?, Persisting container data for volumes, and the Docker Compose overview.
Installing Profilarr
Profilarr runs as two containers: Profilarr itself and the optional parser. The easiest way to run them is Docker Compose, which describes both in a single compose.yml file you can keep, edit, and update with one command. A docker run command works too and is handy for a quick test, but Docker doesn’t save its settings, so you’ll retype it every time you recreate the container.
services:
profilarr:
image: ghcr.io/dictionarry-hub/profilarr:latest
container_name: profilarr
restart: unless-stopped
ports:
- '6868:6868'
volumes:
- ./config:/config
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
# Uncomment if you use a reverse proxy.
# - ORIGIN=https://profilarr.example.com
- PARSER_HOST=parser
- PARSER_PORT=5000
depends_on:
parser:
condition: service_healthy
# Optional, only needed for custom format and quality profile testing.
parser:
image: ghcr.io/dictionarry-hub/profilarr-parser:latest
container_name: profilarr-parser
restart: unless-stopped
expose:
- '5000'Docker Compose keeps your whole setup in one file, compose.yml. Instead of remembering a long command, you describe each container once: which image it uses, which ports and folders it gets, and its settings. Then docker compose up -d starts everything, and running it again after you edit the file applies your changes.
Because it’s just a text file, you can also:
- Keep a copy as a backup, or copy it to a new machine to set up the same thing there
- Save it in a Git repository, which records every change so you can see what changed and undo mistakes
- Let tools like Renovate suggest updates to it automatically
Unraid
On Unraid, install Profilarr from Community Applications instead of writing a compose file.
- Open the Apps tab, search for “Profilarr”, and open the template from dictionarry-hub. Its repository should read
ghcr.io/dictionarry-hub/profilarr:latest. Click Install.
- Check the settings. Unraid fills in the web UI port,
6868, and puts the config folder under appdata, at/mnt/user/appdata/Profilarr. Click Show more settings to seePUID99,PGID100, andUMASK022: Unraid’s usualnobodyuser andusersgroup (see File Permissions). The defaults work for most setups.
- Click Apply. Unraid pulls the image and starts the container.
The template doesn’t include the parser. To use testing, add a second container from ghcr.io/dictionarry-hub/profilarr-parser, then add PARSER_HOST and PARSER_PORT variables to Profilarr. Unraid’s default bridge network doesn’t let containers find each other by name, so either put both containers on the same custom Docker network and use the parser’s container name, or map the parser’s port 5000 and use your server’s IP.
Unraid checks for new images itself and shows available updates in the Docker tab. If you used Profilarr v1 from Community Applications, note that its template has been removed. The v2 template is a separate one.
The Config Folder
Everything Profilarr keeps lives in the folder you mount at /config. Point a new container at the same folder and it picks up exactly where it left off.
While Profilarr runs, SQLite also keeps profilarr.db-wal and profilarr.db-shm next to the database. To back this folder up, see Backups.
File Permissions
Profilarr writes to /config, so the user it runs as needs to be allowed to write there. The container sets that up for you from PUID and PGID.
Linux Basics
Already know how Linux users and permissions work? Skip ahead to Choosing PUID and PGID.
- Users and groups: every file on Linux belongs to one user and one group, identified by number: a user ID (UID) and a group ID (GID). On most Linux systems, the first user you create is
1000. - Permissions: each file says what its owner, its group, and everyone else can do with it: read it, write to it, or run it. A program can only write to a folder if the user it runs as is allowed to.
- Containers: a process inside a container still runs as a UID and GID, and your machine checks those numbers against the files in any folder you mount. If Profilarr runs as
1000but your config folder belongs to1001, Profilarr can’t save anything. - umask: sets the permissions new files start with. The default,
022, means new files can only be written by their owner but can be read by everyone.
Want more detail? The Arch Wiki covers users and groups, file permissions, and umask, and applies to any Linux distribution. LinuxServer.io, where the PUID and PGID convention comes from, explains it in Understanding PUID and PGID.
Choosing PUID and PGID
Set PUID and PGID to the user and group that should own your Profilarr files, usually your own. Run id on your machine to see yours:
$ id
uid=1000(you) gid=1000(you) groups=1000(you)When the container starts, it runs Profilarr as that user and group and makes them the owner of everything in /config, so the files belong to you on your machine too. The Unraid template uses 99 and 100, Unraid’s usual nobody user and users group.
Running as Non-Root
If you start the container as a specific user instead, with user: in your compose file or --user with docker run, the container skips all of that. It doesn’t switch users or change ownership, and PUID and PGID are ignored, so your config folder has to be writable by that user already. It still applies UMASK. This suits hardened setups, and Kubernetes with runAsUser, where containers never start as root.
services:
profilarr:
image: ghcr.io/dictionarry-hub/profilarr:latest
user: '1000:1000'
# ...Health Checks
Both images come with a health check: a small command Docker runs on a timer to ask the container whether it’s working. Depending on the answer, Docker marks the container as healthy or unhealthy. Either one is marked unhealthy after three failed checks in a row.
| Container | Check | Every |
|---|---|---|
| Profilarr | /api/v1/health | 30 seconds, after a 10 second start period |
| Parser | /health | 30 seconds, after a 5 second start period |
/api/v1/health/healthYou’ll see the result in a few places:
docker psshows(healthy)or(unhealthy)in its status column.- The compose example’s
depends_onwaits for the parser to report healthy before it starts Profilarr. - Uptime monitors like Uptime Kuma can watch
/api/v1/healthdirectly. It needs no login, and returns200when Profilarr is healthy or degraded and503when it’s unhealthy, meaning there’s a problem with its database or its linked database repositories.
$ docker ps --format "table {{.Names}}\t{{.Status}}"
NAMES STATUS
profilarr Up 2 hours (healthy)
profilarr-parser Up 2 hours (healthy)Updating
Updating pulls the newest image for the tag you picked, so your release channel decides what you get: latest follows every stable release, 2 stays within v2, develop gets every build, and an exact version like 2.1.0 never changes.
Profilarr v2 moved to ghcr.io/dictionarry-hub/profilarr from santiagosayshey/profilarr, and it can’t use a v1 config. The new name stops auto-updating v1 installs from pulling v2 and breaking. Moving from v1? Change the image by hand and start with an empty /config folder.
By Hand
With Docker Compose, pull the new images and recreate the containers in one go. With docker run, you pull the image, remove the old container, and run your original command again.
docker compose pull
docker compose up -dTo update automatically instead, there are three common options:
Watchtower
Watchtower is the most hands-off option. It checks your containers for new images and recreates them with the same settings when one appears. Updates happen without any review, so you find out what changed after the fact.
The original containrrr/watchtower project was archived in December 2025. The link above is the maintained fork.
Watchtower runs as its own container next to Profilarr, with access to the Docker socket so it can see and recreate other containers. Its command lists the containers it should update, so it leaves everything else alone. The names have to match each service’s container_name:
services:
profilarr:
image: ghcr.io/dictionarry-hub/profilarr:latest
container_name: profilarr
# ...
profilarr-parser:
image: ghcr.io/dictionarry-hub/profilarr-parser:latest
container_name: profilarr-parser
# ...
watchtower:
image: nickfedor/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: profilarr profilarr-parserScheduled Pull
If you’d rather not run another container, a daily cron job does the same thing. This one pulls new images and recreates anything that changed every day at 4am:
0 4 * * * cd /path/to/profilarr && docker compose pull && docker compose up -dRenovate
If your homelab has a dependency bot, you probably don’t need a guide for this!
Rather than updating your containers for you, Renovate proposes each update as a pull request against your compose files, with the release notes attached. Nothing changes until you review and merge it. It needs your compose files in a Git repository.
Exact versions (2.1.0) work out of the box. Renovate sees 2.2.0 as newer and opens a pull request for it.
Moving tags (latest, 2, and develop) keep the same name when they update, so Renovate has no version to compare. Add the image’s digest after the tag, and Renovate tracks the digest instead. It opens a pull request whenever the image behind the tag changes:
image: ghcr.io/dictionarry-hub/profilarr:develop@sha256:<digest>Pin the parser the same way. Then group the two so their updates arrive in one pull request:
{
"packageRules": [
{
"matchPackageNames": [
"ghcr.io/dictionarry-hub/profilarr",
"ghcr.io/dictionarry-hub/profilarr-parser"
],
"groupName": "Profilarr"
}
]
}If another rule in your config also groups these images, such as one for all non-major updates, put this rule after it. When rules set the same option, the later one wins. The docker:pinDigests preset adds digests to your images automatically if you’d rather not add them by hand.
Renovate treats your homelab the way software projects treat their dependencies: every change is reviewed before it goes in. With Watchtower or a scheduled pull, whatever is pushed to a tag ends up running on your server, and you find out what changed afterwards. That includes the rare case of a supply chain attack, where a compromised image is published under a legitimate project’s name.
With Renovate you:
- Read the release notes before anything changes
- See exactly what’s changing in each pull request
- Decide when to merge, or skip an update entirely
- Can make Renovate wait until a release is a few days old with
minimumReleaseAge, so problems have time to surface first
Watchtower’s image cooldown can hold back new images the same way, but it still updates without a review.