Single-Container Install
The all-in-one image runs PicPeak as one container and one Node process, with SQLite as the database. No compose file, no separate PostgreSQL, no Redis, no nginx in front. It is the shortest path from nothing to a working gallery, and it is meant for home servers, NAS boxes, and single photographers or small studios.
docker run -d --name picpeak -p 3000:3000 \
-v picpeak:/data \
ghcr.io/picpeak/picpeak/aio:stableOpen http://localhost:3000/admin and follow the setup wizard.
One volume, no environment variables. Everything that has to survive lives under /data, and the JWT secret is generated on first start and kept there with the rest.
Back up the volume, not just the database. The JWT secret sits in /data/db/jwt.secret and the built-in backup does not include it — it archives your photos and a database dump. Lose that file and every session and gallery link is invalidated, and an encrypted OIDC client secret can no longer be decrypted. A copy of the whole /data volume is what makes the install restorable.
The image is published to both GitHub Container Registry (ghcr.io/picpeak/picpeak/aio) and Docker Hub (picpeak/aio), for linux/amd64 and linux/arm64. The two are the same image — use whichever your host pulls from more easily. Tags follow the release channels: stable (also latest) for releases, beta for the beta channel, and main for the newest build.
Is this the right install for you?
Choosing between this and the compose stack is mostly a question of how many people write at once, not how many photos you have.
Use the single container when you are one photographer or a small studio, you want one thing to run and back up, or you are installing on a NAS where compose is awkward.
Use the compose stack when several staff upload at the same time, you already run PostgreSQL, or you need horizontal scaling. SQLite allows exactly one writer at a time; concurrent uploads queue rather than run in parallel.
You are not locked in. Take a .picpeak backup from the single container and restore it into a full PostgreSQL stack later — see Backup & Restore. That direction (SQLite → PostgreSQL) is the supported upgrade path.
First login
Start the container
Use the docker run above. The first boot creates the database and applies all migrations, which takes a few seconds longer than later starts.
Read the setup token
docker exec picpeak cat /data/db/SETUP_TOKENThe file is written 0600 and is the source of truth. The token is also printed to the container logs on first start (docker logs picpeak), but logs rotate — the file does not, until setup completes and consumes it.
No shell on the host? Open the volume in your NAS file manager and read db/SETUP_TOKEN — it is a plain text file. Note the db/ subdirectory: the token sits beside the database, not at the root of the volume.
The wizard’s on-screen hint shows a docker compose exec backend … command, and the compose stack keeps this file at /app/data/SETUP_TOKEN. Neither applies here — the all-in-one image uses /data/db/.
Create your admin account
Open http://localhost:3000/admin , paste the token, and create the account. The password must be at least 8 characters with an upper-case letter, a lower-case letter and a number — and it also has to survive a strength check, so avoid anything close to a common password.
Continue with Your First Gallery.
What lives where
One volume, /data, holds everything that must survive a container replacement. Mount that and there is nothing else to remember:
| Path | Contents |
|---|---|
/data/db | picpeak.db (plus -wal/-shm), SETUP_TOKEN, and jwt.secret |
/data/storage | Uploaded originals, thumbnails, previews, and archives |
/data/logs | Application logs — inside the volume, so they persist by default |
/data/backup | Output of the built-in backup |
Recreating the container against the same volume preserves everything: the admin account, all galleries, and the applied-migration history. Nothing is re-detected as a fresh install.
Do not mount volumes at /app/data or /app/storage. Older versions of this page documented that layout and it no longer matches the image: /app/storage is a symlink into /data/storage, and mounting over it splits your uploads across two locations while the volume that matters may not be persisted at all.
Configuration
Nothing is required. Everything has a working default, and the rest of the app is configured from the admin UI rather than the environment.
| Variable | Default | Purpose |
|---|---|---|
JWT_SECRET | generated on first start | Signs sessions. Generated into /data/db/jwt.secret and reused from there; set it explicitly only if you want to pin your own value. |
ADMIN_PASSWORD | — | Creates the admin on first boot instead of the setup token. See NAS installs. |
BRAND_TITLE | PicPeak | Browser tab title and social link previews |
BRAND_DESCRIPTION | Photo gallery shared with PicPeak. | Meta description for link previews |
PORT | 3000 | Listening port inside the container |
DATABASE_CLIENT | sqlite3 | Set to pg to use an external PostgreSQL |
Branding, SMTP, storage, and expiry rules are all set in Admin → Settings after first login.
BRAND_TITLE and BRAND_DESCRIPTION are rendered into the page at container start, so changing them means restarting the container — no rebuild needed.
External photo libraries
The AIO container can import from a separate read-only library mount and automatically watch selected event folders. Keep /data for PicPeak’s own data, mount the library at /external-media, and set EXTERNAL_MEDIA_ROOT=/external-media. The same setup works through a NAS container UI. See External Media → All-in-one and NAS container UIs for configuration and the per-event opt-in.
Using an external PostgreSQL
The same image talks to a real PostgreSQL if you would rather not use SQLite. Point it at the server and switch the engine:
docker run -d --name picpeak -p 3000:3000 \
-v picpeak:/data \
-e DATABASE_CLIENT=pg \
-e DB_HOST=postgres.example.internal \
-e DB_PORT=5432 \
-e DB_USER=picpeak \
-e DB_PASSWORD=… \
-e DB_NAME=picpeak \
ghcr.io/picpeak/picpeak/aio:stableThe container waits for the database to accept connections before starting, and logs which engine it resolved. If both a populated SQLite file and a populated PostgreSQL are present it refuses to start rather than guessing.
HTTPS
The image serves plain HTTP and does not terminate TLS. For anything reachable from the internet, put a reverse proxy in front — see Reverse Proxy and SSL Certificates. On a home network reached only over LAN or a VPN, plain HTTP is usually acceptable.
NAS installs
Synology (Container Manager), QNAP (Container Station) and UGREEN (Docker app) can all run this from their container UI. Their image search looks at Docker Hub, so search for picpeak/aio and pick the stable tag; if your NAS lets you add a registry or enter an image name directly, ghcr.io/picpeak/picpeak/aio:stable works the same. Then map port 3000 and mount one volume at /data. No environment variables are needed.
For the full version of this — a UGREEN NAS reachable from the internet on its own domain with a Let’s Encrypt certificate, including DynDNS and router port forwarding — see Example: NAS at Home.
Getting in without a terminal
Most NAS container UIs give you no shell, which makes docker exec … cat /data/db/SETUP_TOKEN awkward or impossible. Two ways round it:
Set the admin up front. Add ADMIN_PASSWORD (and optionally ADMIN_USERNAME and ADMIN_EMAIL) in the container’s environment before the first start. PicPeak then creates the admin during the first boot and skips the setup screen entirely — nothing to find, nothing to read.
ADMIN_USERNAME=admin
[email protected]
ADMIN_PASSWORD=a-password-you-chooseThis only takes effect on the very first start, while no admin exists; setting it later does not change an existing password. Avoid $ in the value, or escape it as $$ — compose reads it as a variable otherwise. Log in, change the password in the admin UI, then remove the variable so it is not left sitting in the container config.
Or read the token with the file manager. Browse to the volume you mounted at /data and open db/SETUP_TOKEN in a text viewer. The db/ subdirectory is the part people miss.
Do not point the volume at an SMB or CIFS share. The container adjusts ownership on its directories at boot, which over SMB can hang startup indefinitely and leave the container unhealthy. Use a native share (Btrfs/ext4 on the NAS itself) for /data.
Updating
docker pull ghcr.io/picpeak/picpeak/aio:stable
docker rm -f picpeak
# re-run the same `docker run` commandMigrations run automatically on start. Back up first — see Backup & Restore. Tags follow the same channels as the other images: stable for releases, beta for the beta channel. On a NAS, use the container UI’s update or re-pull action instead — the /data volume carries over. See Release Channels.
Limits
- One SQLite writer at a time. Simultaneous uploads queue instead of running in parallel. Use PostgreSQL if several people upload at once.
- No bundled TLS. Put a reverse proxy in front for internet-facing installs.
- No horizontal scaling. One container is the deployment model; scale by moving to the compose stack.
- No face recognition. People in Galleries needs a separate ML container this image does not include, and running detection alongside thumbnail generation in one container would slow everything down rather than fail cleanly. The toggle in Settings → Features is disabled here and says so. Use the compose stack if you want it.