---
title: Docker Compose Wizard
url: https://photostructure.com/server/docker-compose-wizard/
description: Interactive Docker Compose wizard for PhotoStructure
date: 2026-02-23
keywords: Docker, installation, permissions, volumes, NAS
---


Before you begin, make sure you have:

- [Docker Engine](https://docs.docker.com/engine/install/) installed (includes
  [Docker Compose v2](https://docs.docker.com/compose/install/); verify with
  `docker compose version`)
- A directory for your [PhotoStructure Library](/faq/library/) with
  [sufficient free space](/getting-started/how-much-disk-space-do-i-need-for-my-photostructure-library/)
- The output of `id -u` and `id -g` from your terminal (for file permissions)

For background on Docker concepts (bind mounts, networking, Synology, Portainer,
etc.), see the [Docker reference page](/server/photostructure-for-docker/).

## Configure your compose.yaml

Fill in the form to generate a `compose.yaml` for PhotoStructure.

<span class="cfg-required">\*</span> indicates required fields.

<div id="compose-configurator" class="compose-configurator">

<div class="cfg-form-header">
  <button type="button" id="cfg-reset" class="cfg-btn-reset"><svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 -960 960 960" width="16" fill="currentColor"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg> Reset form</button>
</div>

<div class="cfg-section">
  <label class="cfg-label" for="cfg-platform">Docker host</label>
  <p class="cfg-desc">Changing this updates the default library directory, PUID, and PGID below.</p>
  <select id="cfg-platform" class="cfg-input">
    <option value="generic" selected>Generic Linux</option>
    <option value="unraid">UnRAID</option>
    <option value="synology">Synology</option>
    <option value="truenas">TrueNAS SCALE</option>
  </select>
  <div id="cfg-unraid-notice" class="cfg-notice cfg-info-notice cfg-hidden">
    See <a href="https://forum.photostructure.com/t/tkohhh-s-recommended-unraid-setup/1281/2">tkohhh's recommended UnRAID setup</a> for additional guidance. We changed your PUID to 99 and PGID to 100, but that may not be correct for your system--please double-check and adjust as needed.
  </div>
  <div id="cfg-synology-notice" class="cfg-notice cfg-info-notice cfg-hidden">
    SSH into your NAS and run <code>id</code> to find your PUID — Synology assigns UIDs sequentially, so it varies per system.
    See <a href="https://forum.photostructure.com/t/synology-nas-tips/1836">Synology NAS tips</a> for more details.
  </div>
  <div id="cfg-truenas-notice" class="cfg-notice cfg-info-notice cfg-hidden">
    TrueNAS SCALE runs apps as <code>568:568</code> (the built-in <code>apps</code> user and group). Replace <code>tank</code> with your actual pool name. Create the dataset in TrueNAS first, then set ownership to <code>568:568</code>.
  </div>
</div>

<div class="cfg-section">
  <div class="cfg-field">
    <label class="cfg-label" for="cfg-library">Library directory <span class="cfg-required">*</span></label>
    <p class="cfg-desc">
      Where PhotoStructure stores your library database, previews, and (optionally) organized copies of your photos.
      Must be readable, writable, and have sufficient free space.
      See <a href="/server/photostructure-for-docker/#library">/ps/library</a> for details.
    </p>
    <input type="text" id="cfg-library" class="cfg-input" required
           value="" placeholder="/mnt/media/PhotoStructure">
  </div>

  <div class="cfg-field">
    <label class="cfg-label">Where is this library directory stored?</label>
    <div class="cfg-radio-group">
      <label class="cfg-radio-label">
        <input type="radio" name="cfg-storage-type" value="local" checked>
        <span class="cfg-radio-text">Local disk</span>
        <span class="cfg-radio-desc">— storage is on this machine or directly attached</span>
      </label>
      <label class="cfg-radio-label">
        <input type="radio" name="cfg-storage-type" value="remote">
        <span class="cfg-radio-text">Network / remote drive</span>
        <span class="cfg-radio-desc">— NFS, SMB/CIFS, or other network filesystem</span>
      </label>
    </div>
    <div id="cfg-network-drive-warning" class="cfg-notice cfg-info-notice cfg-hidden">
      <strong>Required:</strong> Set a cache path
      <a href="#cfg-ps-tmp" id="cfg-scroll-to-tmp">below</a>
      pointing to a <a href="/server/photostructure-for-docker/#tmp">fast local disk</a> with 16–32 GB free.
    </div>
  </div>
</div>

<div class="cfg-section">
  <label class="cfg-label">Additional directories to scan</label>
  <p class="cfg-desc">
    Optional. Directories containing photos and videos you want to import.
    PhotoStructure always scans your library directory — add any <em>other</em> locations here.
    Check <strong>Read-only</strong> to prevent PhotoStructure from writing to that directory.
  </p>
  <div id="cfg-scan-paths"></div>
  <button type="button" id="cfg-add-scan" class="cfg-btn-add">+ Add directory</button>
</div>

<a id="nonfree"></a>

<div class="cfg-section">
  <div class="cfg-notice cfg-info-notice">
    <strong>Videos and photos from your phone</strong>: PhotoStructure now sets up the extra import tools during first launch. As of v2026.4.7, the wizard no longer builds a custom image for codec tools. PhotoStructure prompts for consent and installs them from the container's operating system package manager. See <a href="/getting-started/media-codec-support/">media codec support</a> for details.
  </div>
</div>

<div class="cfg-section">
  <label class="cfg-label">File permissions</label>
  <p class="cfg-desc">
    Docker containers run as root by default, which causes file ownership problems.
    These IDs tell PhotoStructure which user and group should own your library files.
    Run <code>id</code> in your terminal to find yours.
    See <a href="/server/photostructure-for-docker/#puid">PUID/PGID docs</a>.
  </p>
  <div class="cfg-num-group">
    <div>
      <label class="cfg-label" for="cfg-puid">PUID (User ID)</label>
      <p class="cfg-desc">Run <code>id -u</code> to find yours.</p>
      <input type="text" inputmode="numeric" pattern="[0-9]*" id="cfg-puid"
             class="cfg-input" value="1000"
             autocomplete="off" data-1p-ignore data-lpignore="true" data-bwignore>
    </div>
    <div>
      <label class="cfg-label" for="cfg-pgid">PGID (Group ID)</label>
      <p class="cfg-desc">Run <code>id -g</code> to find yours.</p>
      <input type="text" inputmode="numeric" pattern="[0-9]*" id="cfg-pgid"
             class="cfg-input" value="1000"
             autocomplete="off" data-1p-ignore data-lpignore="true" data-bwignore>
    </div>
  </div>
</div>

<details id="cfg-advanced" class="cfg-section">
  <summary class="cfg-label">Advanced options</summary>
  <div class="cfg-advanced-inner">
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-image-tag">Release channel</label>
      <ul class="cfg-desc">
        <li><code>beta</code> has the latest features and is updated frequently.</li>
        <li><code>stable</code> will be the recommended tag soon. For now, use <code>beta</code>.</li>
        <li>See <a href="/about/release-history/">release history</a>.</li>
      </ul>
      <select id="cfg-image-tag" class="cfg-input">
        <option value="beta" selected>beta</option>
        <option value="stable" disabled>stable</option>
      </select>
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-container-name">Container name</label>
      <p class="cfg-desc">
        The Docker container name. Must be unique on your host.
      </p>
      <input type="text" id="cfg-container-name" class="cfg-input"
             value="photostructure" placeholder="photostructure">
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-restart">Restart policy</label>
      <p class="cfg-desc">
        Controls when Docker restarts the container.
        See <a href="https://docs.docker.com/reference/compose-file/services/#restart">Docker docs</a>.
      </p>
      <select id="cfg-restart" class="cfg-input">
        <option value="unless-stopped" selected>unless-stopped — restarts unless manually stopped</option>
        <option value="always">always — always restarts, even after reboot</option>
        <option value="on-failure">on-failure — restarts only on error exit</option>
        <option value="no">no — never restarts</option>
      </select>
    </div>
    <div class="cfg-field">
      <label class="cfg-label">Expose to network</label>
      <p class="cfg-desc">
        Controls whether Docker binds the port to all interfaces (LAN-accessible)
        or <code>127.0.0.1</code> (this machine only).
        PhotoStructure for Docker already listens on all interfaces inside the container,
        so this is enabled by default to match.
        PhotoStructure has no built-in authentication, so only expose it on networks you trust.
        See <a href="/guide/remote-access/">remote access</a> for details.
      </p>
      <label class="cfg-check-label">
        <input type="checkbox" id="cfg-expose" checked>
        <span>Allow connections from other devices on my network</span>
      </label>
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-port">Port</label>
      <p class="cfg-desc">HTTP port for the PhotoStructure web UI. Default is 1787.</p>
      <input type="number" id="cfg-port" class="cfg-input"
             value="1787" min="80" max="65535" placeholder="1787">
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-timezone">Timezone</label>
      <p class="cfg-desc">
        Auto-detected from your browser. Used for assets without timezone metadata.
        <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">Full list of timezones</a>
      </p>
      <input type="text" id="cfg-timezone" class="cfg-input"
             value="" placeholder="America/New_York">
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-log-level">Log level</label>
      <select id="cfg-log-level" class="cfg-input">
        <option value="debug">debug (very verbose, impacts performance)</option>
        <option value="info">info (more detail, may slow imports)</option>
        <option value="warn" selected>warn (recommended)</option>
        <option value="error">error (errors only)</option>
        <option value="fatal">fatal (critical failures only)</option>
      </select>
      <label class="cfg-check-label">
        <input type="checkbox" id="cfg-log-stdout">
        <span>Also log to stdout (visible via <code>docker logs</code>)</span>
      </label>
    </div>
    <div class="cfg-field">
      <label class="cfg-label">Resource limits</label>
      <p class="cfg-desc">
        Optional. Cap how much memory or CPU PhotoStructure can use.
        See <a href="https://docs.docker.com/compose/compose-file/deploy/#resources">Docker docs</a>.
      </p>
      <div class="cfg-num-group">
        <div>
          <label class="cfg-label" for="cfg-mem-limit">Memory limit</label>
          <p class="cfg-desc">e.g., <code>4g</code>, <code>2g</code>, <code>512m</code></p>
          <input type="text" id="cfg-mem-limit" class="cfg-input" placeholder="">
        </div>
        <div>
          <label class="cfg-label" for="cfg-cpu-limit">CPU limit</label>
          <p class="cfg-desc">e.g., <code>2.0</code> (number of cores)</p>
          <input type="number" id="cfg-cpu-limit" class="cfg-input" placeholder=""
                 min="0.25" step="0.5">
        </div>
      </div>
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-name-formatter">Name parsing</label>
      <p class="cfg-desc">
        Controls how face-tagged names are organized into "Who" tags.
        See the <a href="/guide/name-parsing/">name parsing guide</a> for details.
      </p>
      <select id="cfg-name-formatter" class="cfg-input">
        <option value="as-is" selected>as-is — Who/Albert Einstein</option>
        <option value="family/given">family/given — Who/Einstein/Albert</option>
        <option value="family/fullname">family/fullname — Who/Einstein/Albert Einstein</option>
      </select>
      <label class="cfg-label" for="cfg-name-order">Name order</label>
      <p class="cfg-desc">
        Controls how names are parsed into given/family components and displayed in "Who" tags.
      </p>
      <select id="cfg-name-order" class="cfg-input">
        <option value="givenFirst" selected>givenFirst — given name first (Albert Einstein)</option>
        <option value="surnameFirst">surnameFirst — family name first (宮崎 駿)</option>
      </select>
    </div>
    <div class="cfg-field">
      <label class="cfg-label">Additional environment variables</label>
      <p class="cfg-desc">
        See PhotoStructure's <a href="/faq/environment-variables/">environment variables</a> for details
      </p>
      <div id="cfg-env-vars"></div>
      <button type="button" id="cfg-add-env" class="cfg-btn-add">+ Add variable</button>
    </div>
  </div>
</details>

<details id="cfg-custom" class="cfg-section">
  <summary class="cfg-label">Advanced directory setup</summary>
  <div class="cfg-advanced-inner">
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-ps-originals">Originals directory</label>
      <p class="cfg-desc">
        PhotoStructure always imports photos and videos stored here.
        <br>
        When <strong>Copy assets to library</strong> is enabled, newly found media from other locations is also copied here.
        <br>
        Useful for keeping originals on a large drive while previews stay on a faster SSD.
        <br>
        See the <a href="/server/photostructure-for-docker/#originals">bind mount reference for <code>/ps/originals</code></a>.
      </p>
      <input type="text" id="cfg-ps-originals" class="cfg-input" placeholder="">
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-ps-tmp">Local cache directory</label>
      <p class="cfg-desc">
        Must be a directory on a local disk (> 32 GB free, SSD recommended).
        <br>
        Defaults to <code>$libraryDir/.photostructure/tmp</code>.
        <br>
        See the <a href="/server/photostructure-for-docker/#tmp">bind mount reference for <code>/ps/tmp</code></a>.
      </p>
      <input type="text" id="cfg-ps-tmp" class="cfg-input" placeholder="">
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-ps-logs">Log file directory</label>
      <p class="cfg-desc">
      Defaults to <code>$libraryDir/.photostructure/logs</code>
      <br>
      See the <a href="/server/photostructure-for-docker/#logs">bind mount reference for <code>/ps/logs</code></a>.
      </p>
      <input type="text" id="cfg-ps-logs" class="cfg-input" placeholder="">
    </div>
    <div class="cfg-field">
      <label class="cfg-label" for="cfg-ps-config">System settings directory</label>
      <p class="cfg-desc">
        Defaults to <code>$libraryDir/.photostructure/.docker-config</code>.
        <br>
        If you have multiple libraries, they can share system settings by bind-mounting the same directory.
        <br>
        See the <a href="/server/photostructure-for-docker/#config">bind mount reference for <code>/ps/config</code></a>.
      </p>
      <input type="text" id="cfg-ps-config" class="cfg-input" placeholder="">
    </div>
  </div>
</details>

<div class="cfg-output">
  <h3>Your compose.yaml</h3>
  <pre id="compose-output"><code class="language-yaml"></code></pre>
  <div class="cfg-output-buttons">
    <button type="button" id="cfg-download" class="copy-compose-btn"><svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18" fill="currentColor"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg> Download compose.yaml</button>
    <button type="button" id="cfg-copy" class="copy-compose-btn"><svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18" fill="currentColor"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z"/></svg> Copy to clipboard</button>
    <button type="button" id="cfg-share" class="copy-compose-btn"><svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18" fill="currentColor"><path d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h120v80H240v400h480v-400H600v-80h120q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm200-240v-447l-64 64-56-57 160-160 160 160-56 57-64-64v447h-80Z"/></svg> Share configuration</button>
    <button type="button" class="cfg-btn-reset" onclick="document.getElementById('cfg-reset').click()"><svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 -960 960 960" width="16" fill="currentColor"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg> Reset form</button>
  </div>
</div>

</div>

## Next steps

1. Make sure the directories referenced in your compose.yaml exist:

   <pre id="cfg-mkdir-cmd"><code class="language-sh">mkdir -p /mnt/media/PhotoStructure</code></pre>

2. Save the generated YAML as `compose.yaml` into your library directory <code id="cfg-library-dir-hint"></code>

3. Start PhotoStructure:

   <pre id="cfg-cd-cmd"><code class="language-sh">cd /mnt/media/PhotoStructure&#10;docker compose up --detach</code></pre>

4. Open <a id="cfg-open-link" href="http://localhost:1787">http://localhost:1787</a> to complete setup

## Upgrading

```sh
docker compose pull
docker compose up --detach
```

The generated `compose.yaml` includes `pull_policy: always`, so
`docker compose up` will check for newer images automatically. The explicit
`pull` is still recommended to separate the download from the restart.

**Why no Watchtower?** The [Watchtower](https://github.com/containrrr/watchtower)
project was archived in December 2025 and is incompatible with Docker 28+.
If you were using Watchtower, you can safely remove it and use
`docker compose pull && docker compose up --detach` above instead.

## Stopping and restarting

```sh
# Stop
docker compose down

# Start
docker compose up --detach
```

A slow shutdown is normal. It can happen when your library is on a network drive, when the library is large, or when a sync was in progress. PhotoStructure waits up to 2 minutes to stop gracefully. See [why does it take so long to shut down?](/faq/how-to-start-and-stop-photostructure/#why-does-it-take-so-long-to-shut-down)

## Adding or removing directories

To import photos from a new directory (or stop scanning one):

1. Stop PhotoStructure: `docker compose down`
2. Revisit this page (your browser remembers your settings), add or remove the directories, and re-download the compose.yaml.
3. Restart: `docker compose up --detach`

## Uninstalling

```sh
docker compose down
docker compose rm
```

Remove your library, config, tmp, and logs directories as needed. Take care
not to delete photos and videos you want to keep!

## More information

- [Docker reference](/server/photostructure-for-docker/) - system requirements,
  bind mounts, PUID/PGID, networking, Synology, Portainer, soft-delete, Podman
- [Media codec support](/getting-started/media-codec-support/) - first-run
  install flow inside the app, patent background, and manual install for
  non-Docker platforms
- [Advanced settings](/getting-started/advanced-settings/) and
  [environment variables](/faq/environment-variables/)
- [User guide](/getting-started/user-guide/)

