Upgrading
The routine upgrade
docker compose -f docker-compose.production.yml pull
docker compose -f docker-compose.production.yml up -dDatabase migrations run automatically on start, and the backend refuses to serve
against a half-migrated database rather than starting in a broken state. On the
all-in-one image the same applies — re-run your
docker run command with the new image.
Back up first, every time. See Backup & Restore. Some migrations repair data rather than only adding columns, and those cannot be undone by pulling the old image back — the schema change is reversible, the data correction is not.
Which image tag you land on depends on PICPEAK_CHANNEL — see
Release Channels.
Release-specific notes
Only releases that need something from you are listed here. If a version is not mentioned, the routine upgrade above is all it needs.
v3.46.x → v3.132.1
This release promotes roughly two months of beta work onto the stable channel at once, so it carries more change than a normal stable patch.
One-way. Migration 186 deletes duplicate external-photo rows and 187
rewrites stored external paths with no record of the previous base — both
irreversible by design. Once an instance is on v3.132.1 it cannot be moved back
to a v3.46.x image. Take a backup you have actually tested restoring.
Two visible side effects on first boot, both verified against a real v3.46.13 database:
- Every lightbox preview regenerates. Migration
188clears all stored preview keys, so each photo re-renders its preview the first time it is viewed. Nothing is lost, but on a large library expect a noticeable CPU spike — and S3 egress, if your originals live there — spread over the first hours of use rather than a one-off cost during the upgrade itself. - A fixed pixel height on gallery tiles is rewritten. Migration
181replaces any.photo-card img { height: <N>px }withheight: 100%, including a rule you wrote yourself in Custom CSS. All seven layouts size the card and expect the image to fill it, so a pixel height there detached the image from its tile. If you set one deliberately, your tiles will change.
Nothing is switched on for you. Face recognition, PicTransfer, newsletters, CRM,
accounting, contracts, quotes and projects all stay off by default, existing
permission grants are projected forward so no account loses capability, and the
optional picpeak-ml sidecar is not started unless you ask for it
(docker compose --profile faces up -d).
One thing you will be asked: anonymous usage reporting is off by default, but an existing install gets a single opt-in prompt on the first admin login after the update. Declining is a permanent answer.
Why the version jumped from 3.46.13 to 3.132.1. Stable and beta count releases independently — beta bumps on every merge, stable only on a promotion — so the numbers drift far apart for identical code. A promotion now pins stable to beta’s current base version. The jump is the two channels meeting, not 85 releases of change — stable v3.132.1 carries the same work as beta v3.132.1-beta.0.
Permission, session and backup hardening
Builds containing PicPeak PR #1477 , PicPeak PR #1478 , PicPeak PR #1483 , PicPeak PR #1485 , PicPeak PR #1486 , PicPeak PR #1489 , PicPeak PR #1491 , PicPeak PR #1493 , PicPeak PR #1495 and PicPeak PR #1497 upgrade with the routine steps. Migration 217 only adds two columns, and the Docker Compose secrets volumes are created and filled on the next docker compose up. A few behaviours change:
Check your logo folders. Only image files are served from storage/uploads/logos and storage/uploads/favicons now. Older versions could leave other files there; look for any and delete them.
- Mail servers. Changing an SMTP, IMAP or mail-account server’s host, port, username or TLS setting requires entering its password again. Sent invitation and password-reset emails cannot be resent from Messages. See Email Notifications.
- SSO. Changing the issuer URL or client ID requires entering the client secret again, and role mappings can only target roles within the editor’s own permissions. See Admin SSO.
- Backups. The backup destination, database inclusion, restores and
.picpeakimports are Super Admin only. See Backup and Restore. - Database dumps. In builds containing PicPeak PR #1502 , installs without a writable
/backupwrite the database dump under their backup destination instead of failing withEACCES: permission denied, mkdir '/backup'; installs with a/backupmount keep/backup/database. See Where the database dump is written. - Events and roles. Roles other than Super Admin and Admin see only their own and ownerless events; Admin sees every event but not other owners’ share links; thumbnail settings need
settings.edit. See Roles & Permissions. - Galleries. Changing a gallery password, client PIN or client link signs out the sessions opened with the old one, guest uploads respect the photo cap, and the admin preview follows the session timeout. See Creating Events.
- Feedback. The per-event feedback rate-limit switch is gone; the instance-wide limits apply. See Feedback.
- 3.46.x is end of life. See Release Channels.
October 2026: hardening and proofing
Builds containing PicPeak PR #1809 , PicPeak PR #1813 , PicPeak PR #1788 , PicPeak PR #1792 , PicPeak PR #1793 and — on the beta channel — PicPeak PR #1795 , PicPeak PR #1796 , PicPeak PR #1797 , PicPeak PR #1799 and PicPeak PR #1800 upgrade with the routine steps. On the beta channel all of them are in v3.161.0-beta.0; on stable, the ones that apply there ship with v3.134.0. The migrations run on their own: on the beta channel 261 (notification dismissals), 262 (rating limit) and 263 (SQLite expiry values); on stable 238 (SQLite expiry values) and 239 (notification dismissals). A few behaviours change:
- Roles. Tracker code settings and the S3 bucket list are Super Admin only; assigning customers to events needs
customers.events; mailbox actions needemail.edit; the bell shows a role its own events; deleting an administrator who still owns events needs a Super Admin. See Roles & Permissions. - Notifications. Clear all hides entries for the admin who clicks it and no longer deletes the activity log. See Guest activity in the bell.
- Logins. Lockout is per account and address; the second factor has its own counter per account. See Security Settings.
- Analytics. The tracker no longer runs in the customer portal or on short links. See Analytics.
- Slideshow. Regenerating or disabling a slideshow link ends the sessions opened with it. See Live Slideshow.
- Restore. A restore fails when any file is missing, enforces
restore_max_file_size_mb, and signs everyone out afterwards;.picpeakimports only accept the folders an export writes. See Backup and Restore. - Backup manifests no longer contain S3 or SSH credentials. Manifests from older builds still do; rotate the keys if those files were exposed.
- Workflow approvals. An approval link sent by email is valid for 14 days unless the gate sets another time-out, and never longer than 90 days. Links from older builds that had no expiry are treated as expired; the approval itself stays open and can be decided from the admin inbox.
- Contracts and invoices. PDFs uploaded by signers and PDFs arriving by mail are inspected before use; only the newest payment-check link of an invoice works. See Contracts, Invoices and Incoming Invoices.
- Gallery expiry on SQLite. Galleries now expire on their date whatever form the date was stored in; overdue galleries expire on the first check after the upgrade. An expiry date-time sent to the API without a time zone is read as UTC. See Gallery Management.
- Custom CSS. A
<in custom CSS is stored as the CSS escape\3c, which browsers read as the same character, and theme colours must be real colour values (hex,rgb(),hsl()or a basic colour name); anything else falls back to the default. See Custom CSS. - Fonts. The public landing page serves Inter from the PicPeak server; it no longer loads anything from Google Fonts (builds containing PicPeak PR #1787 ).
- Building from source. The frontend build needs Node.js 20 or newer and stops with a message on older versions. The Docker images are not affected.
- Installer.
picpeak-setup.shwrites.envreadable by its owner only and accepts--admin-password-fileand--smtp-pass-file. See Simple Install.
Beta: v3.165 to v3.175
The security review tracked after the October 2026 release changed several deployment defaults on the beta channel. Most of these changes reached stable with v3.134.2 to v3.134.7; see Stable: v3.133.3 to v3.134.7 for the stable numbers. The migrations run on their own; the ones that cannot be undone are listed under One-way changes.
Do these before you pull. Each one breaks a working install if it is missing.
- Compose behind another reverse proxy (Traefik, Nginx Proxy Manager, Caddy, Cloudflare Tunnel in front of the frontend port) with no
TRUST_PROXYin.env: addTRUST_PROXY=2. From v3.174.2-beta.0 the compose file defaults to1, and without the change every visitor shares the proxy’s address — one rate-limit budget, and five failed logins from anyone lock out logins for everyone for 15 minutes. See Reverse Proxy. - All-in-one image behind a reverse proxy: add
-e TRUST_PROXY=1(one per proxy hop). From v3.174.2-beta.0 the image trusts no proxy by default; without it all visitors share one budget and every request counts as plain HTTP, so cookies lose theirSecureflag. See Single-Container Install → HTTPS. - Backend port reached from another machine (
http://<host>:3001): from v3.167.2-beta.0 the compose file publishes it on127.0.0.1only. Use the frontend port instead, or setPICPEAK_BACKEND_BIND_ADDRESS=0.0.0.0and a matchingTRUST_PROXY. - External PostgreSQL with
DB_SSL=true: from v3.167.2-beta.0 the certificate and host name are verified. With a private or self-signed CA, setDB_SSL_CA(orDB_SSL_SERVERNAMEwhenDB_HOSTis an IP address). See Environment Variables → Database. - Mail server on a private address that was entered in the admin UI (SMTP, IMAP or a mail account) rather than through
SMTP_HOST/SMTP_PORT: list it inMAIL_PRIVATE_ENDPOINTS(for examplesmtp://mail.lan:587,imap://mail.lan:993), or sending and polling stop (v3.167.2-beta.0). - Analytics or OIDC on a private address: list the origin in
INTEGRATION_PRIVATE_ORIGINS. - Rsync backups: put the destination’s verified host key in a
known_hostsfile next to the SSH key, or pointBACKUP_SSH_KNOWN_HOSTSat one. Unknown or changed host keys are refused, nothing is trusted on first use, the connection uses port 22 and ignores local SSH config aliases. See Backup and Restore → SSH host keys.
Network and installer
- The compose file passes
COOKIE_SECURE=autoandENABLE_HSTS=falseexplicitly.autowas already the production default; an unrecognisedCOOKIE_SECUREvalue now meansautowith a warning. SetCOOKIE_SECURE=trueandENABLE_HSTS=trueonce the site is only reached over HTTPS. - The installer writes
PICPEAK_BIND_ADDRESS=127.0.0.1,TRUST_PROXY=2,COOKIE_SECURE=trueandENABLE_HSTS=truein its reverse-proxy mode. An unattended install without a domain needs--allow-insecure-http; an interactive one asks you to confirm plain HTTP. See Simple Install. /healthanswers only{"status":"ok"}(v3.175.1-beta.0). Monitoring that readpidoruptimefrom it needs another source.RATE_LIMIT_NETWORK_MULTIPLIERis no longer read (v3.175.0-beta.0), and the image-security tab’s rate limiting and auto-block are gone. Remove the variable.- The compose file mounts
./update/requestand./update/statusfor in-app updates (v3.165.0-beta.0). Docker creates a root-owned./update/on the nextdocker compose up -d, even while the feature stays off.
Analytics (v3.167.2-beta.0)
- Custom tracker snippets are disabled and PicPeak no longer loads the Umami or Rybbit script; its own client sends page views through
/api/analytics/tracker/events. Select Umami, Rybbit or None under Settings → Analytics and save to clear an old snippet. - Production collectors must use HTTPS. An analytics dashboard is only embedded when it runs on a host outside PicPeak’s cookie scope (not the same host on another port); otherwise it opens in a new tab.
- Visitors whose browser sends Global Privacy Control are not counted; Do Not Track is honoured unless you switch that off (v3.169.0-beta.0). See Analytics.
Backups
- Manifests are signed (v3.167.2-beta.0). Compose generates the key in the
picpeak-secretsvolume; native and all-in-one installs createbackup-manifest.keyin the data directory with the first backup. Copy the key off the host: without it no backup can be authenticated on a new host.BACKUP_MANIFEST_REQUIRE_KEYEDis gone — every manifest needs a valid signature. - Backups made before the upgrade are not authenticated and are refused by a normal restore. Take a new backup right after upgrading. To restore an old one, use the one-time
BACKUP_MANIFEST_RECOVERY_SHA256/BACKUP_MANIFEST_RECOVERY_REASONapproval on an isolated host. See Backup and Restore → Manifest signing key. - Every run is a complete, standalone copy (v3.167.3-beta.0); plan the disk space for full copies. Full and file restores from a legacy incremental run are refused; database-only and selective restores still work.
- With S3 as primary storage, backups copy the media through the storage adapter (v3.167.6-beta.0). Configure the target bucket before restoring onto a fresh host.
.picpeakimports run as a coordinated maintenance restore (v3.168.1-beta.0). They need Linux, the native build (in the Docker images) andSTORAGE_PATHon a local filesystem; elsewhere only the import is switched off. Restart every backend instance after a committed restore.
Access changes
- Client links need the current private link token together with the client PIN (v3.167.2-beta.0). Send clients the current client link; sessions already signed in stay valid.
- PicTransfer upload codes shorter than 10 characters are replaced by migration
272. Upload links sent for those transfers stop working; the migration logs the affected transfers so you can send the new links. - CRM records follow their creator (v3.167.3-beta.0): quotes, invoices, contracts and documents are visible to the admin who created them and to super admins. From v3.171.0-beta.0 expenses and incoming invoices follow the same rule (recorder, or creator of the linked gallery). Linking a gallery to an invoice needs its creator or a super admin. A studio that used the built-in Admin role as bookkeeper for everyone needs a super admin for that view.
- External source folders need owners (migration
274, v3.167.2-beta.0). A super admin assigns folders to admins; until then, existing external-source galleries cannot be browsed or rescanned by anyone but a super admin. See External Media.
Capacity limits
- Upload admission (migrations
276,277, v3.167.3-beta.0): public uploads (gallery guests, PicTransfer file requests) have lifetime, concurrency and hourly allowances; authenticated uploads are only bounded while in flight. Raise them withPUBLIC_UPLOAD_LIMITS_JSON/ADMIN_UPLOAD_LIMITS_JSON. See Environment Variables → Upload limits. - Incoming mail (migration
275) has backlog and rate budgets; a message over a budget waits on the mail server. Retention stays off unless you setEMAIL_INTAKE_RETENTION_DAYS. - Image and media processing runs in worker processes with memory and time limits (migrations
278,279, v3.167.4-beta.0 and v3.167.5-beta.0). The defaults accept what earlier versions accepted.
Media and storage after the upgrade
- Photo trash (migration
294, v3.172.0-beta.0): a photo an admin deletes goes to the gallery’s trash for 30 days, so its disk space is not freed at once. Set Trash retention (days) to0under Settings → General for the old immediate delete. - Derived images without EXIF (migration
311, v3.174.2-beta.0): thumbnails, previews and heroes no longer carry GPS position, camera and copyright. A background pass rebuilds the existing ones, one photo at a time, starting with link-preview covers. Expect extra CPU (and S3 traffic) for a while after the upgrade;RENDITION_REFRESH_DISABLED=trueskips it. Downloads keep their metadata. - Video metadata (v3.174.2-beta.0): Strip location and device data from played videos under Settings → General is on by default. Browser-compatible videos get a metadata-free copy of about the original’s size, and on first start every existing video is queued once (S3 originals are downloaded to be probed).
One-way changes on beta
- Migration
273stores invitation and password-reset tokens as digests (v3.167.2-beta.0). Outstanding invitations and reset links stop working on upgrade; the migration logs how many, and names any administrator whose temporary password it had to reset. Database guards then refuse writes from older backends, so update every replica promptly and do not go back to an older image. - Migration
313seals gallery passwords and client PINs in queued mail and WhatsApp messages withEMAIL_QUEUE_ENCRYPTION_KEY(orJWT_SECRET) and masks them in sent ones (v3.175.1-beta.0). Keep that key unchanged while mail is pending. - Migration
272replaces short PicTransfer upload codes; the old links cannot be restored.
Stable: v3.133.3 to v3.134.7
The stable patch releases after v3.132.1 carry the stable twins of the beta fixes above, with their own migration numbers. Each release upgrades with the routine steps; the notes list what needs something from you.
Before you pull v3.134.7: a Compose install behind another reverse proxy with no TRUST_PROXY in .env needs TRUST_PROXY=2, and an all-in-one container behind a proxy needs -e TRUST_PROXY=1. The reasons are the same as on beta, above.
v3.133.3 — migrations 229 (login attempts) and 230.
- A
JWT_SECRETshorter than 32 characters, or one of the published example values, stops the backend at startup. Replace it before upgrading; values encrypted with the old secret (such as an OIDC client secret) need to be entered again. - Analytics servers and OIDC providers on a private address need an entry in
INTEGRATION_PRIVATE_ORIGINS. - A forced password reset now revokes the account’s API tokens (migration
230applies this to accounts already in that state). Issue new tokens afterwards.
v3.133.4 — migrations 233 (stored document paths relative to the storage root), 234 (re-keys legacy anonymous guest feedback on first contact) and 235. No action needed.
v3.133.5 — a private S3 endpoint for backups has to be approved by a super admin before it is used, and the S3 connection test is a real one. See Backup and Restore → Private S3 endpoints.
v3.133.6 — migration 236 (SQLite only): queued emails left to the column default now come due. A pending email that had been waiting more than a day is marked failed with a reason; retry it from System Health → Failures. Resized and watermarked downloads keep the photo’s EXIF, XMP and IPTC.
v3.133.8 — changing the OIDC issuer URL or client ID, and the database backup destination, needs a super admin. ENABLE_HSTS now takes effect behind the bundled nginx.
v3.133.9 — migration 237 (hero focal point). No action needed.
v3.134.0 — migrations 238 and 239; see October 2026: hardening and proofing.
v3.134.2 — migrations 240 to 246. This release carries most of the hardening; check each item.
- Network: the backend port is published on
127.0.0.1only (PICPEAK_BACKEND_BIND_ADDRESS);COOKIE_SECURE=autoandENABLE_HSTS=falseare passed explicitly; the installer needs--allow-insecure-httpfor an unattended plain-HTTP install. - PostgreSQL TLS:
DB_SSL=trueverifies certificate and host name. SetDB_SSL_CAfor a private CA before upgrading. - Mail: private mail servers configured in the admin UI need
MAIL_PRIVATE_ENDPOINTS. - Analytics: custom tracker snippets are disabled; select Umami, Rybbit or None under Settings → Analytics and save to clear an old snippet. Production collectors must use HTTPS.
- Backups: manifests are signed with a key that must be kept off the host; older backups are not authenticated and need the one-time recovery approval to be restored. Every run is a standalone full copy, and full or file restores from legacy incremental runs are refused. Rsync needs a pre-approved host key. Take a new backup after upgrading and copy the key. See Beta: Backups above for the details, which are the same.
- Access: client links need the current link token with the PIN; short PicTransfer upload codes are replaced (migration
241) and their old links stop working; external source folders need owners assigned by a super admin (migration243); CRM documents follow their creator. - Capacity: public and authenticated upload admission (migrations
245,246) with the same defaults as beta; incoming-mail budgets (migration244). - One-way: migration
242stores invitation and reset tokens as digests. Outstanding invitations and reset links stop working, and older backends cannot write to the database afterwards.EMAIL_QUEUE_ENCRYPTION_KEY(defaultJWT_SECRET) encrypts pending recovery mail; keep it unchanged while such mail is waiting.
v3.134.3 — migrations 247 and 248: image and media processing run in worker processes with memory and time limits. With S3 as primary storage, backups copy the media through the storage adapter; configure the target bucket before restoring onto a fresh host, and if inline database dumps are off, create a new scheduled dump after upgrading. Uploads no longer fail with UPLOAD_STORAGE_LOW on nearly full volumes or filesystems that report no inodes.
v3.134.4 — migrations 249 and 250: .picpeak imports run as a coordinated maintenance restore. They need Linux, the native build (in the Docker images) and STORAGE_PATH on a local filesystem; restart every backend instance after a committed restore.
v3.134.5 — every app page answers X-Robots-Tag: noindex, so only the landing page can be indexed and galleries can no longer be made indexable. A verified gallery viewer gets a request budget of their own. Visitors whose browser sends Do Not Track or Global Privacy Control are not counted by analytics.
v3.134.6 — expenses and incoming invoices follow the per-admin owner rule: an admin who is not a super admin sees only what they recorded and what is booked to their own galleries.
v3.134.7 — migration 251.
- Proxy trust: the compose file defaults
TRUST_PROXYto1and the all-in-one image to0(see the warning above). Rate limits count an IPv6 /64 as one client. - Image protection: at enhanced and maximum protection, photos are served through the normal authenticated photo route; the unused secure-image token routes are removed, and
RATE_LIMIT_NETWORK_MULTIPLIERhas no effect any more. Remove it. - EXIF: new thumbnails, previews and heroes carry no EXIF or GPS. Stable has no background refresh: thumbnails and previews already stored keep their metadata until they are regenerated (Regenerate All Thumbnails in the thumbnail settings).
/healthanswers only{"status":"ok"}. Uploaded SVGs are served with a strict Content Security Policy.- One-way: migration
251seals gallery passwords and client PINs in queued mail and WhatsApp messages and masks them in sent ones. KeepEMAIL_QUEUE_ENCRYPTION_KEY(orJWT_SECRET) unchanged while mail is pending.
Not on stable yet: the /backup mount in the stable branch’s compose file, in-app updates, the photo trash, video metadata stripping and the rendition refresh.
If an upgrade goes wrong
- The backend will not start. Check its logs first
(
docker compose logs backend); a failed migration names itself and leaves the database untouched rather than half-applied. - Galleries load but images are missing. Usually the
API rate limiter rather
than the upgrade — a
429is a completed request, so the browser console shows nothing. - Everyone is locked out of login, or every visitor gets
429, behind a reverse proxy.TRUST_PROXYis missing or too low, so all visitors share the proxy’s address. See Reverse Proxy. - You need to go back. Restore the backup. Pulling the previous image is not enough on a release whose notes above say it is one-way.