# Docker

> Installing and updating Profilarr with Docker Compose.

A page from the Profilarr documentation by https://github.com/santiagosayshey, last updated 2026-10-01 (commit: https://github.com/Dictionarry-Hub/profilarr.com/commit/92a5c54a149c3c94cd38a300e6082dc70a368f93). Web version: https://profilarr.com/installation/docker. Edit on GitHub: https://github.com/Dictionarry-Hub/profilarr.com/edit/develop/src/routes/(docs)/installation/docker/+page.svx

Already comfortable with Docker? Skip ahead to [Installing Profilarr](#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:tag` on GitHub's registry, where the tag after the colon chooses a [release channel](https://profilarr.com/installation#release-channels).
- **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.

> **Info:** New to Docker? Docker's own guides go deeper on each of these: [What is a container?](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-a-container/), [What is an image?](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-an-image/), [Persisting container data](https://docs.docker.com/get-started/docker-concepts/running-containers/persisting-container-data/) for volumes, and the [Docker Compose overview](https://docs.docker.com/compose/).

## Installing Profilarr

Profilarr runs as two containers: Profilarr itself and the optional [parser](https://profilarr.com/installation#the-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.

`Docker Compose`

```yaml
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 run`

```sh
docker network create profilarr

# Optional, only needed for custom format and quality profile testing.
docker run -d \
  --name profilarr-parser \
  --network profilarr \
  --restart unless-stopped \
  ghcr.io/dictionarry-hub/profilarr-parser:latest

docker run -d \
  --name profilarr \
  --network profilarr \
  --restart unless-stopped \
  -p 6868:6868 \
  -v "$(pwd)/config:/config" \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=Etc/UTC \
  -e PARSER_HOST=profilarr-parser \
  -e PARSER_PORT=5000 \
  ghcr.io/dictionarry-hub/profilarr:latest
```

> **Info:** 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](#renovate) suggest updates to it automatically

### Unraid

On Unraid, install Profilarr from Community Applications instead of writing a compose file.

1. 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**.

![The Profilarr template in Community Applications, with the ghcr.io/dictionarry-hub/profilarr:latest repository](https://profilarr.com/images/docs_docker_unraid_template[style=light].png)

2. 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 see `PUID` `99`, `PGID` `100`, and `UMASK` `022`: Unraid's usual `nobody` user and `users` group (see [File Permissions](#file-permissions)). The defaults work for most setups.

![The Profilarr container settings in Unraid, with more settings shown](https://profilarr.com/images/docs_docker_unraid_settings[style=light].png)

3. Click **Apply**. Unraid pulls the image and starts the container.

The template doesn't include the [parser](https://profilarr.com/installation#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.

```text
config/
├── backups/  # Backup archives
│   └── backup-2026-09-27-043000.tar.gz
├── data/
│   ├── databases/  # A Git clone of each linked database
│   │   └── <id>/
│   └── profilarr.db  # Settings, Arr instances, and linked databases
└── logs/  # One log file per day
    └── 2026-09-27.log
```

While Profilarr runs, SQLite also keeps `profilarr.db-wal` and `profilarr.db-shm` next to the database. To back this folder up, see [Backups](https://profilarr.com/installation/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](#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 `1000` but your config folder belongs to `1001`, 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.

> **Info:** Want more detail? The Arch Wiki covers [users and groups](https://wiki.archlinux.org/title/Users_and_groups), [file permissions](https://wiki.archlinux.org/title/File_permissions_and_attributes), and [umask](https://wiki.archlinux.org/title/Umask), and applies to any Linux distribution. LinuxServer.io, where the `PUID` and `PGID` convention comes from, explains it in [Understanding PUID and PGID](https://docs.linuxserver.io/general/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:

`Terminal`

```sh
$ 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:`](https://docs.docker.com/reference/compose-file/services/#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.

`compose.yml`

```yaml
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 |

You'll see the result in a few places:

- `docker ps` shows `(healthy)` or `(unhealthy)` in its status column.
- The compose example's `depends_on` waits for the parser to report healthy before it starts Profilarr.
- Uptime monitors like Uptime Kuma can watch [`/api/v1/health`](https://profilarr.com/api/v1#getHealth) directly. It needs no login, and returns `200` when Profilarr is healthy or degraded and `503` when it's unhealthy, meaning there's a problem with its database or its linked database repositories.

`Terminal`

```sh
$ 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](https://profilarr.com/installation#release-channels) 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.

> **Warning:** 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`

```sh
docker compose pull
docker compose up -d
```

`docker run`

```sh
docker pull ghcr.io/dictionarry-hub/profilarr:latest
docker stop profilarr
docker rm profilarr

# Then run your original docker run command again.
# Repeat for profilarr-parser if you use it.
```

To update automatically instead, there are three common options:

### Watchtower

[Watchtower](https://github.com/nicholas-fedor/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.

> **Info:** 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`:

`compose.yml`

```yaml
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-parser
```

### Scheduled 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:

`crontab`

```sh
0 4 * * * cd /path/to/profilarr && docker compose pull && docker compose up -d
```

### Renovate

> If your homelab has a dependency bot, you probably don't need a guide for this!

Rather than updating your containers for you, [Renovate](https://docs.renovatebot.com) 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:

`compose.yml`

```yaml
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:

`renovate.json`

```json
{
  "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.

> **Info:** 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.

---

Index of this site's Markdown pages: https://profilarr.com/llms.txt
