PhotoStructure for Docker
- Read the pros and cons of Docker and Node editions
- What’s a βvolume?β (if you want your library portable
across machines, create
.uuidfiles) - PhotoStructure user guide
π₯οΈ System requirements
The docker images 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 for more information.
π Using docker run directly
The 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:
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):
-v /mnt/photos:/photos:ro \
See bind-mount setup below for the full list of configurable directories.
ποΈ Docker bind-mount setup
PhotoStructure for Docker needs at least one bind-mount to store your library. The 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”).
Your library directory (/ps/library)
- This is where your PhotoStructure 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/tmpbind-mount, this must be a local disk.
Originals directory (/ps/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
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)
This is PhotoStructure’s cache directory, only required if /ps/library is stored on a network or remote drive.
/ps/tmpmust 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)
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)
This is where PhotoStructure stores system 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
/photosor/albumsworks well.Don’t bind-mount into any previously-existing file or directory, like
/etc,/bin,/opt, or/usr.
- To prevent PhotoStructure from modifying imported directories, use read-only bind-mounts, with a couple caveats:
- PhotoStructure can’t write a
.uuidfile to read-only volumes, so you’ll need to create one manually. Without a.uuid, PhotoStructure can’t build stablepsfile://URIs, which means filesystem tags 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/librarymust be read-writable, so bind-mount your photos and videos to a different directory.
- PhotoStructure can’t write a
π« Directories to omit
PhotoStructure will import all mounted directories recursively. PhotoStructure
respects NoMedia files, and will exclude those directories from being
imported. (learn more)
βοΈ Advanced settings
PhotoStructure has additional 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.
π€ 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.
Running as non-root is more secure.
How do I specify the userid and groupid?
PhotoStructure uses linuxserver.io-style PUID/PGID environment
variables.
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
- Troubleshooting file permission issues from inconsistent PUID/PGID
π‘ Networking setup
Configuring the HTTP port
PhotoStructure listens on port 1787 by default. To use a different port:
- Set the
PS_HTTP_PORTenvironment variable - Update the
--publishport mapping to match
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:
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:
- Stop old containers:
docker ps -a | grep photostructurethen stop/remove duplicates - Use a different port:
-e PS_HTTP_PORT=8080 --publish 8080:8080 - 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:
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.
# 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:
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 or discord.
ποΈ Soft-delete support
As of v2024.4, PhotoStructure complies with the XDG trash standard.
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:
Ensure there is a
$volume/.Trash-$uiddirectory, where$uidis the user id PhotoStructure runs as (typically1000on Ubuntu). PhotoStructure will attempt to create this directory with correct permissions if it doesn’t already exist.Alternatively, you can create a
$volume/.Trashdirectory. 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/$uidsubdirectory with0700permissions.Ensure the user that PhotoStructure is running as can write to that directory
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 subdirectories 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 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 is an open-source Docker management GUI, useful if you prefer not to run docker commands directly.
Installation instructions for Linux are here.
Here’s a video walkthrough from forum member @avdp (the Portainer UI may have changed since this was recorded):
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. Community tips are in the Synology NAS Tips forum thread.
Install on Synology
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 checkControl Panel > User & Group. On Synology, the first user is typically UID1026and the defaultusersgroup is GID100.Alternatively, you can create a dedicated
photostructureuser and add it to theusersgroup (GID100). Since your primary account is also inusers, 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 for a walkthrough of this approach.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.Generate your
compose.yamlwith the Docker Compose Wizard. Use the UID/GID from step 1 forPUID/PGID, and Synology-style paths (like/volume1/docker/photostructure) for the library directory.Create a Project in Container Manager:
- Open
Main Menu > Container Manager - Click the Project tab, then Create
- Name your project (e.g.,
photostructure) - Set the path to where you want the
compose.yamlstored - Paste the output from the Docker Compose Wizard into the editor
- Click Done to launch
- Open
Verify PhotoStructure is running at
http://YOUR-NAS-IP:1787.
Do not select “high privilege.” Give PhotoStructure at least 2GB of RAM.
Alternative: via SSH
If you prefer the command line, enable SSH on your
NAS,
copy your compose.yaml to the NAS, and run:
sudo docker compose up --detach
Upgrade on Synology
If your compose.yaml includes pull_policy: always (the default from the
Docker Compose Wizard):
- Open Container Manager β Project
- Select your project β Action β Stop
- Select your project β Action β Build
Container Manager pulls the newest image and recreates the container.
For SSH users, see the upgrade instructions.
What about Docker Volumes?
Docker supports volumes, which are “the preferred mechanism for persisting data generated by and used by Docker containers.”
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 – 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.pidfor single-instance hosts/var/run/user/$PUID/photostructure.pidif 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
SIGINTorSIGTERMto 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
- 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
July 2022
- Added Portainer section

