---
title: PhotoStructure for Docker
url: https://photostructure.com/server/photostructure-for-docker/
description: Docker reference: system requirements, bind mounts, PUID/PGID, networking, Synology, and more
date: 2023-11-09
keywords: Docker, installation, permissions, volumes, Synology, port-conflicts
---


{{% tip %}}
**Looking to set up PhotoStructure?** Use the **[Docker Compose Wizard](/server/docker-compose-wizard/)** to generate a ready-to-use `compose.yaml`.

This page is a reference for Docker concepts, bind mounts, networking, and
platform-specific instructions.
{{% /tip %}}

- [Read the pros and cons of Docker and Node editions](/server/photostructure-for-servers/)
- [What's a “volume?”](/faq/what-is-a-volume/) (if you want your library portable
  across machines, [create `.uuid` files](/faq/what-is-a-volume/#how-to-manually-add-uuid-files))
- [PhotoStructure user guide](/user-guide)

## 🖥️ System requirements

The [docker images](https://hub.docker.com/r/photostructure/server) run under
`linux/amd64` (x64) and `linux/arm64` Docker hosts.

PhotoStructure containers require at least 2GB of RAM during imports. You'll see
`ENOMEM` errors if you run within a 1GB container. PhotoStructure imports run
faster when more RAM and more CPU are allocated to the container.

The x64 docker image is built on a recent CPU with `SSE4` and `AVX` extensions:
x64 CPUs that are less than a decade old should work fine, but if your server is
running an older processor (like an AMD Phenom II), the image may not spin up.
The arm64 image runs on Raspberry Pi 4 and later, as well as other arm64 hosts.

Docker Desktop (macOS/Windows) works but expect reduced I/O performance
compared to native Linux Docker Engine due to the virtualization layer.

## 🎬 Video and HEIF/HEIC support

The base image doesn't bundle FFmpeg or HEIF/HEIC tooling. On first run,
PhotoStructure asks whether to install them and runs `apt-get install` under a
scoped sudoers rule. Your consent is stored in `/ps/config/codec-install-consent.env` and `/ps/library/.photostructure/codec-install-consent.env`, so tools
auto-restore after container rebuilds -- delete that file or visit localhost:1787/welcome/tools to reset consent.

See [media codec support](/getting-started/media-codec-support/) for more information.

## 🐋 Using `docker run` directly

The [Compose Wizard](/server/docker-compose-wizard/) is recommended because it
generates a working configuration with all bind mounts and permissions. If you
prefer `docker run`, here is a minimal example:

```sh
docker run \
  --name photostructure \
  --restart unless-stopped \
  --publish 1787:1787 \
  -e PUID=$(id -u) \
  -e PGID=$(id -g) \
  -e TZ=$(cat /etc/timezone 2>/dev/null || echo UTC) \
  -v /path/to/library:/ps/library \
  photostructure/server:beta
```

Replace `/path/to/library` with an actual directory path. To import photos from
other directories, add more `-v` flags (append `:ro` for read-only):

```sh
  -v /mnt/photos:/photos:ro \
```

See [bind-mount setup](#docker-bind-mount-setup) below for the full list of
configurable directories.

<a id="volume-setup"></a>

## 🗃️ Docker bind-mount setup

PhotoStructure for Docker needs at least one
[bind-mount](https://docs.docker.com/storage/bind-mounts/) to store your
library. The [Docker Compose Wizard](/server/docker-compose-wizard/) handles this for
you. The reference below explains what each mount does.

PhotoStructure writes to up to 5 directories, all configurable. Only `/ps/library` is
required. The other `/ps/...` directories fall back to subdirectories within
your library if not mounted (thanks to ["docker easy-mode"](https://forum.photostructure.com/t/new-easy-mode-for-docker-coming-in-v2-1/1278)).

### Your library directory (`/ps/library`) {#library}

- This is where your [PhotoStructure Library](/faq/library/) will be stored.
- This bind-mount is required.
- It must be readable and writable by the PhotoStructure container, and have
  sufficient free space.
- If there is no `/ps/tmp` bind-mount, **this must be a local disk.**

### Originals directory (`/ps/originals`) {#originals}

PhotoStructure always scans this directory for photos and videos to import.
Mount your originals volume here to keep them separate from your library
database and previews.

When **Copy assets to library** is enabled, PhotoStructure copies newly found
media from other locations into `/ps/originals` instead of your library
directory. This lets you keep originals on a large drive while the library
database and previews stay on a faster SSD. See the [hybrid library forum
post](https://forum.photostructure.com/t/hybrid-photostructure-libraries/775)
for more background.

If this bind-mount doesn't exist, PhotoStructure stores copied assets in your
library directory.

**Note:** If you open your library on another computer without this volume
mounted, full-screen zoom and non-transcoded video won't work. You'll need to
set `originalsDir` on each machine that accesses the library.

### Local cache directory (`/ps/tmp`) {#tmp}

This is PhotoStructure's cache directory, only required if `/ps/library` is stored on a network or remote drive.

- `/ps/tmp` **must** be on a local disk.
- This volume should have at least 16-32 GB free.
- If you've got an SSD, use that: this directory will see a lot of reads and writes.
- If you can, pick (or create) a volume that doesn't have data integrity
  protection, on-the-fly compression, or on-the-fly file de-duplication. This
  will make imports faster as well as reduce system load.
- PhotoStructure automatically prunes old and unused files from the scratch
  directory: it shouldn't grow linearly with your library.

If this bind-mount doesn't exist, PhotoStructure will use
`/ps/library/.photostructure/cache` instead.

### Log file directory (`/ps/logs`) {#logs}

This is where PhotoStructure stores logfiles. Normally PhotoStructure should be
mostly quiet, unless you set `PS_LOG_LEVEL` to `info` or `debug`.

PhotoStructure deletes log files older than 1 week automatically.

If this bind-mount doesn't exist, PhotoStructure will use
`/ps/library/.photostructure/logs` instead.

### System settings directory (`/ps/config`) {#config}

This is where PhotoStructure stores [system
settings](/getting-started/advanced-settings/).

If this bind-mount doesn't exist, PhotoStructure will use
`/ps/library/.photostructure/docker-config` instead.

## 🗄️ External directories to import

- To add directories you want to import, add them as bind volumes to your container.

- A root directory like `/photos` or `/albums` works well.

- **Don't** bind-mount into any previously-existing file or directory, like `/etc`, `/bin`, `/opt`, or `/usr`.

<a id="readonly" />

- To prevent PhotoStructure from modifying imported directories, use [**read-only bind-mounts**](https://docs.docker.com/storage/volumes/#use-a-read-only-volume), with a couple caveats:
  - PhotoStructure can't write a `.uuid` file to read-only volumes, so you'll need to [create one manually](/faq/what-is-a-volume/#how-to-manually-add-uuid-files). Without a `.uuid`, PhotoStructure can't build stable [`psfile://` URIs](/faq/what-is-a-volume/#how-photostructure-identifies-volumes), which means [filesystem tags](/faq/whats-a-hierarchical-tag/) won't work and your library won't be portable across machines or container rebuilds.
  - PhotoStructure won't be able to save metadata changes (rotation, edits) back to these files.
  - `/ps/library` **must** be read-writable, so bind-mount your photos and videos to a different directory.

## 🚫 Directories to omit

PhotoStructure will import all mounted directories recursively. PhotoStructure
respects `NoMedia` files, and will exclude those directories from being
imported. ([learn more](/faq/how-to-hide-directories/))

## ⚙️ Advanced settings

PhotoStructure has additional [settings](/settings) that are only configurable
outside of the PhotoStructure UI.

Docker users may find it's easiest to use environment variables to override
settings. [**Read more about PhotoStructure's use of environment
variables.**](/faq/environment-variables/)

<a id="puid"></a>
<a id="pgid"></a>

## 👤 Running PhotoStructure for Docker as a non-root user

### ⚠️ Why not run as root?

Dockerized applications run as root by default. If PhotoStructure runs as
root, the files in your library will be owned by root, which can cause
permission issues with the [original files that PhotoStructure copies into your
library](/getting-started/automatic-library-organization/).

Running as non-root is more secure.

### How do I specify the userid and groupid?

PhotoStructure uses [linuxserver.io-style `PUID`/`PGID` environment
variables](https://docs.linuxserver.io/general/understanding-puid-and-pgid).

### Default PUID/PGID values

If `$PUID` or `$PGID` is not set, PhotoStructure checks the current owner of
the system `settings.toml` or library `settings.toml`. If both are missing,
it defaults to `1000` for both, which is the userid and groupid assigned to
the first non-system user on Ubuntu and Fedora.

### Run as root

If you want PhotoStructure to run as root, set `PUID=0` and `PGID=0`.

### Why is PhotoStructure running as `node`?

Rather than creating a new user in PhotoStructure's docker container, we just
change the userid and groupid of `node` to match `$PUID` and `$PGID`.

### `chown` on startup

PhotoStructure's `docker-entrypoint.sh` script will `chown -R` your
library support directories if the current owner doesn't match `$PUID`, so
that after we drop root privileges to be `node`, we still can read and write to your library.

Disable these `chown` calls by setting the environment variable `PS_NO_PUID_CHOWN=1`.

### More PUID/PGID info

- [Original PUID/PGID feature request](https://forum.photostructure.com/t/add-linuxserver-style-puid-and-pgid-support-to-the-photostructure-docker-image/370)
- [Troubleshooting file permission issues from inconsistent PUID/PGID](https://forum.photostructure.com/t/files-in-ps-tmp-owned-by-root/1597/2)

## 📡 Networking setup

### Configuring the HTTP port

PhotoStructure listens on port **1787** by default. To use a different port:

1. Set the `PS_HTTP_PORT` environment variable
2. Update the `--publish` port mapping to match

```sh
docker run \
  -e PS_HTTP_PORT=8080 \
  --publish 8080:8080 \
  ... # other options
  photostructure/server:beta
```

**Important:** Both the environment variable (`PS_HTTP_PORT`) and the container
port in `--publish` must match. The host port (left side of `:`) can be
different if you want to expose PhotoStructure on a different external port.

By default, the Docker image listens on all interfaces inside the container, and
Docker publishes the port on all host interfaces when you use `--publish
1787:1787`. As of v2026.4.7, PhotoStructure has no built-in authentication, so
only do this on networks you trust.

To restrict access to the Docker host, bind the published port to
`127.0.0.1`:

```sh
docker run \
  --publish 127.0.0.1:1787:1787 \
  ... # other options
  photostructure/server:beta
```

Then open <http://localhost:1787> on the host. Other devices on your LAN will
not be able to reach PhotoStructure.

### ⚠️ Port conflict errors (`EADDRINUSE`)

If PhotoStructure cannot bind to its port, check `docker logs photostructure` for
the error message.

**Common fixes:**

1. **Stop old containers:** `docker ps -a | grep photostructure` then stop/remove duplicates
2. **Use a different port:** `-e PS_HTTP_PORT=8080 --publish 8080:8080`
3. **Check for conflicts:** Ensure no other container uses the same host port

PhotoStructure exits cleanly on port conflicts, allowing Docker's `--restart`
policy to retry after you fix the issue.

### Reverse proxy configuration

If you set up a reverse HTTPS proxy via nginx and find that dynamic updates
(like the progress status) aren't showing up, [add this to your nginx
config](https://serverfault.com/a/801629/53135):

```
proxy_http_version 1.1;
proxy_set_header Connection "";
```

### Docker health check

PhotoStructure's Docker image includes a built-in health check that pings the web
server every 30 seconds. The health check automatically uses `PS_HTTP_PORT` if
you've configured a custom port.

```sh
# Quick status (shows health in STATUS column)
docker ps

# Detailed health info (requires jq: https://jqlang.github.io/jq/)
docker inspect --format='{{json .State.Health}}' photostructure | jq

# Without jq - just show health status
docker inspect --format='{{.State.Health.Status}}' photostructure
```

**Health check timing:**

- **Start period:** 60 seconds (allows time for first-run initialization)
- **Interval:** 30 seconds
- **Timeout:** 10 seconds
- **Retries:** 3 failures before marking unhealthy

If the container shows as "unhealthy", the web server isn't responding. Check
`docker logs photostructure` for error details.

## 🔧 Troubleshooting

If PhotoStructure doesn't spin up, add these environment variables to see what's
happening:

```yaml
environment:
  - "PS_LOG_LEVEL=info"
  - "PS_LOG_STDOUT=true"
```

Use `PS_LOG_LEVEL=warn` for less output, `PS_LOG_LEVEL=debug` for more.

Still stuck? Visit the [forum](https://forum.photostructure.com/) or
[discord](/go/discord).

<a id="soft-delete"></a>

## 🗑️ Soft-delete support

As of v2024.4, PhotoStructure complies with the [XDG trash
standard](https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html).

**If PhotoStructure can write to the top directory of a bind mount that you want
to delete photos or videos from, PhotoStructure will handle soft-deletes
automatically.**

If PhotoStructure **cannot** create new directories to the top directory of a
bind mount, you will need to do some initial setup before soft deletes on that
volume will work properly.

For every bind mount you want PhotoStructure to be able to soft-delete, say, `$volume`:

1. Ensure there is a `$volume/.Trash-$uid` directory, where `$uid` is the user
   id PhotoStructure runs as (typically `1000` on Ubuntu). PhotoStructure will
   attempt to create this directory with correct permissions if it doesn't
   already exist.

   Alternatively, you can create a `$volume/.Trash` directory. The permissions
   on these directories must allow all trashing users to write, and the sticky
   bit must be set (if the filesystem supports it), so PhotoStructure can create
   a `$volume/.Trash/$uid` subdirectory with `0700` permissions.

2. Ensure the user that PhotoStructure is running as can write to that directory

3. Ensure the user can write to the directory that the photo or video will be soft-deleted from

### Where's my trash can contents?

Files will only show up on a Linux desktop trashcan if the docker bind mount volume is mounted at the **same depth**: the XDG standard doesn't look in mountpoint **sub**directories for `.Trash` or `.Trash-$uid`

### What about `~/.local/share/Trash`?

PhotoStructure for Docker will not attempt to use `$XDG_DATA_HOME/Trash` unless the directory already exists.

**This doesn't comply with the XDG trash specification:** it is a precaution against storing your soft-deleted files ephemerally in the docker container, and having a container upgrade unexpectedly hard-delete your trash can.

If you want to use this directory, bind-mount it with correct permissions into your PhotoStructure container under `/home/node/.local/share/Trash`, but know that soft-deletes will now require file _copies_ rather than file _moves_, which will be substantially slower depending on volume transfer speeds.

### What happens if PhotoStructure cannot soft-delete a file?

If PhotoStructure cannot move a file into a suitable `.Trash` directory, PhotoStructure will hard-delete the file. A [sync report](/go/sync-reports/) will describe what action was performed for every file.

### How do I disable soft-deletes?

PhotoStructure will irreversibly delete photos and videos when you "empty trash" via the user interface if the `PS_TRY_SOFT_DELETES` setting is `false`.

Windows and macOS don't comply with the Freedesktop trash standard, so running Docker on those platforms won't support "restore" or "empty trash". You'd need to find and delete `$volume/.Trash-$UID` manually. It's easier to just set `PS_TRY_SOFT_DELETES=false`.

## 🎛️ Portainer instructions

[Portainer CE](https://docs.portainer.io/) is an open-source Docker management GUI, useful if you prefer not to run `docker` commands directly.

[Installation instructions for Linux are here](https://docs.portainer.io/start/install-ce/server/docker/linux).

Here's a video walkthrough from forum member [@avdp](https://forum.photostructure.com/u/avdp/summary) (the Portainer UI may have changed since this was recorded):

<iframe width="560" height="315" src="https://www.youtube.com/embed/X_klyzKot4A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## Synology Docker instructions

PhotoStructure runs on x64 and arm64 Synology NAS devices running DSM 7.2 or
later. Install the **Container Manager** package via `Main Menu > Package
Center` (it was called "Docker" in older DSM versions).

For a walkthrough with screenshots, see [MK Library's PhotoStructure for
Synology guide](https://www.mklibrary.com/photostructure-for-synology/).
Community tips are in the [Synology NAS Tips forum
thread](https://forum.photostructure.com/t/synology-nas-tips/1836/10).

### Install on Synology

1. **Find your UID and GID.** PhotoStructure should run as your normal DSM
   user so that library files have the same ownership as the rest of your
   photos. SSH in and run `id your-username`, or check `Control Panel > User
& Group`. On Synology, the first user is typically UID `1026` and the
   default `users` group is GID `100`.

   Alternatively, you can create a dedicated `photostructure` user and add it
   to the `users` group (GID `100`). Since your primary account is also in
   `users`, both accounts can access the same files. This limits what the
   container can reach without affecting your own access. See the [TRaSH
   Guides Synology
   setup](https://trash-guides.info/File-and-Folder-Structure/How-to-set-up/Synology/)
   for a walkthrough of this approach.

2. **Create shared folders** for the library and bind mounts using `Control
Panel > Shared Folder`. A common convention is
   `/volume1/docker/photostructure`. Make sure your user has read/write
   access.

3. **Generate your `compose.yaml`** with the [Docker Compose
   Wizard](/server/docker-compose-wizard/). Use the UID/GID from step 1 for
   `PUID`/`PGID`, and Synology-style paths (like
   `/volume1/docker/photostructure`) for the library directory.

4. **Create a Project** in Container Manager:
   1. Open `Main Menu > Container Manager`
   2. Click the **Project** tab, then **Create**
   3. Name your project (e.g., `photostructure`)
   4. Set the path to where you want the `compose.yaml` stored
   5. Paste the output from the Docker Compose Wizard into the editor
   6. Click **Done** to launch

5. Verify PhotoStructure is running at `http://YOUR-NAS-IP:1787`.

Do **not** select "high privilege." Give PhotoStructure at least 2GB of RAM.

{{% tip %}}
If your Synology has an NVMe cache drive, point `/ps/library` (or at least
`/ps/tmp`) at the NVMe volume. The library database and preview generation are
much faster on SSD.
{{% /tip %}}

#### Alternative: via SSH

If you prefer the command line, [enable SSH on your
NAS](https://www.synology.com/en-global/knowledgebase/DSM/tutorial/General_Setup/How_to_login_to_DSM_with_root_permission_via_SSH_Telnet),
copy your `compose.yaml` to the NAS, and run:

```sh
sudo docker compose up --detach
```

### Upgrade on Synology

If your `compose.yaml` includes `pull_policy: always` (the default from the
[Docker Compose Wizard](/server/docker-compose-wizard/)):

1. Open **Container Manager → Project**
2. Select your project → **Action → Stop**
3. Select your project → **Action → Build**

Container Manager pulls the newest image and recreates the container.

{{% tip %}}
If your `compose.yaml` was created before `pull_policy` was added, either
re-generate it from the [Docker Compose Wizard](/server/docker-compose-wizard/),
or add `pull_policy: always` under the `image:` line. Without it, Build reuses
the cached image for non-`:latest` tags like `:beta`.
{{% /tip %}}

For SSH users, see the [upgrade instructions](/server/docker-compose-wizard/#upgrading).

## What about Docker Volumes?

Docker supports [volumes](https://docs.docker.com/storage/volumes/), which are
"the preferred mechanism for persisting data generated by and used by Docker
containers."

{{% tip %}}
Bind mounts are recommended — your library stays portable across editions and operating systems. Use an SSD for the library database, previews, and scratch directory.
{{% /tip %}}

Docker Volumes may offer slightly better I/O performance, but bind mounts give you the flexibility to move your library to another computer or switch container hosts (like Podman).

## Podman support

Forum user iamphotocat submitted [podman installation
instructions](https://forum.photostructure.com/t/photostructure-with-podman/2002) -- thanks!

## Pidfiles

You can use a process ID file ("pidfile") to control PhotoStructure instances.

Set the `PS_PIDFILE` environment variable to an absolute path that PhotoStructure
can write to. Good defaults:

- `/var/run/photostructure.pid` for single-instance hosts
- `/var/run/user/$PUID/photostructure.pid` if running multiple concurrent libraries with different user IDs

If the parent directory doesn't exist, PhotoStructure will try to create it, but
it must be writable by the `PUID`/`PGID` user and group.

Once running, the file contains the main process ID. To shut down:

- Send `SIGINT` or `SIGTERM` to that PID, or
- Run `photostructure --stop --pidfile=/path/to/pidfile`

The `--stop` command exits with 0 after graceful shutdown, or non-zero if
something went wrong. Make sure the pidfile paths match between start and stop.

## Edit history

### April 2026

- Video and HEIF/HEIC support is now configured on first run inside the app
  (was previously opt-in via the Docker Compose wizard's Dockerfile build)

### February 2026

- Moved Docker setup to interactive [Docker Compose Wizard](/server/docker-compose-wizard/)
- This page is now a reference for Docker concepts, bind mounts, and platform-specific instructions
- Removed patent-encumbered FFmpeg/HEIF from base Docker image

### April 2024

- Added soft-delete instructions

### November 2023

- Integrated [Docker easy-mode](https://forum.photostructure.com/t/new-easy-mode-for-docker-coming-in-v2-1/1278)

### July 2022

- Added Portainer section

