# Docker Support > [!WARNING] Breaking Changes > > - The docker image now runs as user 1001 and group 1001. Make sure that the permissions on your volumes allow user 1001 to read and write to them or see the User section below for other options, or if you're not sure. > - `SLEEP_TIME` is now set to `-1` by default. This means the image will run once and exit. If you were relying on the previous default, you'll need to explicitly set the `SLEEP_TIME` environment variable to `30m` to replicate the previous behavior. > - The docker image now ignores the values in `Settings.json` for `Books` and `InProgress`. You can now change the folder that books are saved to by using the `LIBATION_BOOKS_DIR` environment variable. ## Disclaimer The docker image is provided as-is. We hope it can be useful to you but it is not officially supported. ## Configuration > [!WARNING] Encrypted tokens need the master key in Docker > > If Docker logs show `Failed to decrypt ExistingAccessToken` (or your scan finds no books after copying Windows config), you copied an `AccountsSettings.json` whose auth tokens are encrypted with a key that normally stays on the desktop OS secret store. > > **Preferred fix (keep encryption):** On the desktop machine, export the master key: > > - **GUI:** Settings -> Important -> **Export encryption key...** > - **CLI:** `LibationCli export-master-key libation-master.key` > > Copy `libation-master.key` into the same Docker config folder as `AccountsSettings.json` (mounted at `/config`). Libation loads it automatically. Alternatively set `LIBATION_MASTER_KEY_FILE` to the key path, or `LIBATION_MASTER_KEY` to the Base64-encoded key. Treat the key file like a password. > > **Other fixes:** On Windows, **Settings -> Important**, uncheck **Store authentication tokens encrypted**, convert existing tokens to plaintext when prompted, then re-copy `AccountsSettings.json`. Or skip copying Windows accounts and use `login-external` / `import-account` inside the container (see below). > > **Last resort (not recommended):** If encryption stays on and you never supply a key, Libation may auto-create `libation-master.key` under the Libation files directory and warn loudly (console, log, and `libation-master.key.NOTICE.txt`). That key only unlocks tokens encrypted with it afterward - it will not decrypt tokens from another machine. Prefer the steps above. When creating accounts in Docker, use `--libationFiles /config` so any key file persists on the host mount. > > **Encrypt-on-save without a key:** If tokens are already usable (plaintext, or ciphertext you can unlock) but encryption is preferred and no protector is available, a later save (for example after an access-token refresh) does **not** stop the container. Libation logs an error such as `Failed to encrypt identity field ... Saving as plaintext so the app can continue` and re-saves those fields as plaintext. That is intentional so headless Docker keeps working. Prefer supplying a master key (above) if you want tokens encrypted at rest. This does **not** fix `Failed to decrypt` for tokens encrypted on another machine without the key. > > Details: [FAQ](/docs/frequently-asked-questions#docker-finds-no-new-books-failed-to-decrypt-existingaccesstoken) · [Troubleshooting](/docs/advanced/troubleshoot#failed-to-decrypt-existingaccesstoken-docker-finds-no-new-books) > [!WARNING] NTFS filesystem limitations > > NTFS filesystems (Windows, and NTFS-formatted external drives on Linux/Mac) do not support colons (`:`) in filenames. Since many audiobook titles contain colons (e.g., "Title: A Subtitle"), downloads may produce invalid filenames. > > **Solution:** Configure custom replacement characters in `Settings.json` to replace colons with compatible characters. See [Command Line Interface - Set custom replacement characters](/docs/advanced/command-line-interface#set-custom-replacement-characters) for configuration examples. > [!TIP] Pacing downloads > > To stop a scheduled container from downloading your whole library at once, see [Daily download limit](/docs/features/daily-download-limit#docker-and-the-command-line). It is off unless you add the keys to your mounted `Settings.json`. Download counts live in Libation's database, so they survive container restarts when that database is on a volume. Configuration in Libation is handled by two files, `AccountsSettings.json` and `Settings.json`. These files can usually be found in the Libation folder in your user's home directory. The easiest way to configure these is to run the desktop version of Libation and then copy them into a folder, such as `/opt/libation/config`, that you'll volume mount into the image. `Settings.json` is technically optional, and, if not provided, Libation will run using the default settings. Additionally, the `Books` and `InProgress` settings in `Settings.json` will be ignored and the image will instead substitute it's own values. If tokens in that file are encrypted on the desktop, also copy the exported `libation-master.key` (see the encrypted-tokens warning above). ### Adding Audible accounts without the GUI If you run Libation on a server or in Docker and do not want to copy `AccountsSettings.json` from a desktop install, you can create or update accounts with LibationCli (same binary as in the image under `/libation/LibationCli`, also available on `PATH` as `LibationCli`): - `import-account` — Import an account from a JSON file exported by [mkb79's audible-cli](https://github.com/mkb79/audible-cli) (or Libation's own compatible export). Example: `LibationCli import-account /path/to/account.json` - `login-external` — Browser-based sign-in: the CLI prints an Audible login URL; you open it in a normal browser, sign in, then paste the final URL from the address bar back into the terminal. Example: `LibationCli login-external --account you@example.com --locale us` If standard input is not a TTY (for example in some automation), pass the final URL with `--response-url "https://..."` instead of pasting interactively. - `list-accounts` — List configured accounts and whether each has valid stored credentials (and scan-on/off). Example: `LibationCli list-accounts` or `LibationCli list-accounts --bare` for tab-separated output. For full syntax, overrides, and the `--libationFiles` option (or the `LIBATION_FILES_DIR` environment variable) when your Libation data directory is not the default, see [Command Line Interface](/docs/advanced/command-line-interface). Docker tip: The entrypoint script copies your mounted config into an internal path before running LibationCli. To add or refresh accounts against the mounted volume, `docker exec` into a running container and point LibationCli at `/config` (so changes persist on the host): ```bash docker exec -it libation LibationCli login-external --libationFiles /config --locale us --account you@example.com ``` You can also start a container with an interactive shell instead of the default liberate loop (see [Interactive Shell](#interactive-shell) below), or run LibationCli on any host where you can point `--libationFiles` (or `LIBATION_FILES_DIR`) at the folder that you later mount as `/config` on the server. ## Running Once the configuration files are copied, the docker image can be run with the following command. ```bash sudo docker run -d \ -v /opt/libation/config:/config \ -v /opt/libation/books:/data \ --name libation \ --restart=always \ rmcrackan/libation:latest ``` By default the container will scan for new books once and download any new ones. This is configurable by passing in a value for the `SLEEP_TIME` environment variable. For example, if you pass in `10m` it will keep running, scan for new books, and download them every 10 minutes. ```bash sudo docker run -d \ -v /opt/libation/config:/config \ -v /opt/libation/books:/data \ -e SLEEP_TIME='10m' \ --name libation \ --restart=always \ rmcrackan/libation:latest ``` ### Interactive Shell If you pass a command to `docker run` instead of using the default, the entrypoint script still performs its normal setup (copying config into place, applying `Books`/`InProgress` overrides, and locating/creating the database) but then runs your command instead of the liberate loop. This gives you a shell with the same layout the container uses at runtime, without needing an already-running container to `docker exec` into: ```bash sudo docker run --rm -it \ -v /opt/libation/config:/config \ -v /opt/libation/books:/data \ rmcrackan/libation:latest bash ``` From there you can run `LibationCli import-account ...`, `LibationCli login-external ...`, or any other one-off command against the mounted config. ## Environment Variables | Env Var | Default | Description | | -------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- | | SLEEP_TIME | -1 | Length of time to sleep before doing another scan/download. Set to -1 to run one. | | LOG_LEVEL | | Set to `debug` to enable debug-level log messages from the entrypoint script. | | LIBATION_BOOKS_DIR | /data | Folder where books will be saved | | LIBATION_CONFIG_DIR | /config | Folder to read configuration from. | | LIBATION_DB_DIR | /db | Optional folder to load database from. If not mounted, will load database from `LIBATION_CONFIG_DIR`. | | LIBATION_DB_FILE | | Name of database file to load. By default it will look for all `.db` files and load one if there is only one present. | | LIBATION_CREATE_DB | true | Whether or not the image should create a database file if none are found. | | LIBATION_CONNECTION_STRING | | Connection string for Postgresql. If not present, Libation uses the default sqlite. | | LIBATION_MASTER_KEY_FILE | | Path to a raw 32-byte master key file (from `export-master-key`). If unset, Libation also looks for `libation-master.key` under the Libation files / config directory. | | LIBATION_MASTER_KEY | | Base64-encoded 32-byte master key (alternative to a key file). | ## User This docker image runs as user `1001`. In order for the image to function properly, user `1001` must be able to read and write the volumes that are mounted in. If they are not, you will see errors, including [sqlite error](https://github.com/rmcrackan/Libation/issues/1060), [Microsoft.Data.Sqlite.SqliteException](https://github.com/rmcrackan/Libation/issues/1110), [unable to open database file](https://github.com/rmcrackan/Libation/issues/1113), [Microsoft.EntityFrameworkCore.DbUpdateException](https://github.com/rmcrackan/Libation/issues/1049) If you're not sure what your user number is, check the output of the `id` command. Docker should normally run with the number of the user who configured and ran it. If you want to change the user the image runs as, you can specify `-u :`. For example, to run it as user `2000` and group `3000`, you could do the following: ```bash sudo docker run -d \ -u 2000:3000 \ -v /opt/libation/config:/config \ -v /opt/libation/books:/data \ --name libation \ --restart=always \ rmcrackan/libation:latest ``` If the user it's running as is correct, and it still cannot write, be sure to check whether the files and/or folders might be owned by the wrong user. You can use the `chown` command to change the owner of the file to the correct user and group number, for example: `chown -R 1001:1001 /mnt/audiobooks /mnt/libation-config` ## Troubleshooting - **`Failed to decrypt ExistingAccessToken` (scan finds no books after copying Windows config):** See the [encrypted-tokens warning](#configuration) at the top of Configuration, plus [Troubleshooting](/docs/advanced/troubleshoot#failed-to-decrypt-existingaccesstoken-docker-finds-no-new-books) and the [FAQ](/docs/frequently-asked-questions#docker-finds-no-new-books-failed-to-decrypt-existingaccesstoken). - **`Failed to encrypt identity field ... Saving as plaintext`:** Harmless for operation - encryption could not run (usually no master key / OS store in the container), so tokens were saved as plaintext and the run continued. Supply a master key if you want encryption at rest. See the [encrypted-tokens warning](#configuration). - **Library scan appears to hang in Docker:** Try setting `ImportEpisodes` to `false` in `Settings.json` on your config volume and run again. That turns off the extra episode/podcast catalog requests during import and helps narrow down whether the stall is in that path versus auth or the network. ## Advanced Database Options The docker image supports an optional database mount location defined by `LIBATION_DB_DIR`. This allows the database to be mounted as read/write, while allowing the rest of the configuration files to be mounted as read only. This is specifically useful if running in Kubernetes where you can use Configmaps and Secrets to define the configuration. If the `LIBATION_DB_DIR` is mounted, it will be used, otherwise it will look for the database in `LIBATION_CONFIG_DIR`. If it does not find the database in the expected location, it will attempt to make an empty database there. ## Logging LibationCli already writes a `LogYYYYMM.log` file (rolling monthly) using the same logging setup as the desktop apps — no extra configuration is required to generate it. However, in the docker image the log is written to an internal path (`/config-internal`) that isn't persisted or mounted by any of the examples above, so it disappears when the container is removed. To keep it around, use one of the following: - **Mount the internal config path**, e.g. add `-v /opt/libation/logs:/config-internal` to your `docker run` command. Note that this directory also holds the staged copies of `AccountsSettings.json`/`Settings.json` and the database symlink, which are regenerated from `/config`/`/db` on every container start. - **Point the log file at an already-mounted directory** by adding a `Serilog` section to your `Settings.json` (in your `/config` volume) with a `File` sink `path` pointing somewhere persisted, such as `/data/Log.log`. Libation uses an existing `Serilog` section as-is apart from filling in the size-rolling arguments described below when they are missing: ```json "Serilog": { "MinimumLevel": "Information", "WriteTo": [ { "Name": "File", "Args": { "path": "/data/Log.log", "rollingInterval": "Month", "fileSizeLimitBytes": 10485760, "rollOnFileSizeLimit": true, "retainedFileCountLimit": 20, "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception} {Properties:j}", "hooks": "LibationFileManager.FileSinkHook, LibationFileManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" } } ], "Using": [ "Dinah.Core", "Serilog.Exceptions" ], "Enrich": [ "WithCaller", "WithExceptionDetails" ] } ``` ### Log size The log rolls on **both** the calendar month and file size: a new file is started every 10 MB (`Log202608.log`, `Log202608_001.log`, ...) and the 20 newest files are kept, so the logs stay around 200 MB at most and any single file is small enough to attach to a bug report. A frequent cron schedule and many accounts is what makes this matter: every run logs a startup block and every account's library scan logs a line per page of results, so several runs an hour across a dozen-plus accounts produces several MB a day. `rollingInterval` alone does not bound that — before this was the default, a single month's log could reach tens of MB, and Serilog's own 1 GB ceiling would eventually stop it logging altogether until the month rolled over. To change any of it, set `fileSizeLimitBytes`, `rollOnFileSizeLimit` or `retainedFileCountLimit` yourself in `Settings.json`; Libation only fills in the ones you leave out. Lowering `MinimumLevel` is not an option (`Information` is the lowest that still records what a bug report needs), but a smaller `retainedFileCountLimit` bounds total disk use. ## Getting Help As mentioned above: docker is not officially supported. I'm adding this at the bottom of the page for anyone serious enough to have read this far. If you've tried everything above and would still like help, you can open an [issue](https://github.com/rmcrackan/Libation/issues). Please include `[docker]` in the title. There are also some docker folks who have offered occasional assistance who you can tag within your issue: `@ducamagnifico` , `@wtanksleyjr` , `@CLHatch` , `@oxivanisher`. **Reminder** that these are just friendly users who are sometimes around. They're _not_ our customer support.