Skip to main content
Every image and video uploaded to PostQueen, from the media library, the composer or the public API, goes to one storage backend. There are two: Each file can be up to 1 GB. Accepted types are JPEG, PNG, GIF, WebP, AVIF, BMP and TIFF images, and MP4 and MOV video. What a network will publish is usually far smaller, and each network decides that itself.

Networks fetch your media by URL

PostQueen stores each file’s full address with the post. Several networks download the file from that address themselves instead of receiving it from PostQueen: Instagram, Facebook, Threads, Slack, TikTok photo posts and everything on TikTok Business. For those, the address must be reachable from the public internet over HTTPS. A laptop or a private network cannot serve it, so posts with media to those networks fail there. Networks that receive the file from PostQueen, such as X, LinkedIn and Bluesky, work from anywhere.

Local storage

The Compose file ships ready for it:
and mounts the postqueen-uploads volume at /uploads. Keep those values in the official image: its nginx serves exactly the /uploads folder, and a different UPLOAD_DIRECTORY writes files that are never served. NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY is written without slashes.
UPLOAD_DIRECTORY is required with local storage. Without it the backend does not start: it stops with STORAGE_PROVIDER is "local" but UPLOAD_DIRECTORY is not set.
Several containers need a volume that every one of them mounts (ReadWriteMany on Kubernetes), or R2.

Move to a new address

Local storage saves each file’s address as FRONTEND_URL + /uploads/.... Change FRONTEND_URL later, for a new domain or a move from http to https, and existing media keeps pointing at the old address: it stops loading and cannot be published. Put the old address in PREVIOUS_FRONTEND_URLS next to the new FRONTEND_URL:
Recreate the app container so it reads the new values. A plain docker restart or docker compose restart keeps the old container and its old environment. In the folder that holds docker-compose.yaml, recreate only the postqueen service:
The database, Redis and Temporal keep running. Avoid docker compose down for a settings change: on a host that runs other Compose services in the same project, it stops them too. On Kubernetes, see Kubernetes with Helm. At start, the backend rewrites every stored address under the old one to the new one, in the media library, posts, drafts and channel avatars, and logs what it moved as an [uploads] line.
  • List every earlier address, separated by commas, each written in full with http:// or https://.
  • An address is skipped, with a warning in the log, when none of its files exist under UPLOAD_DIRECTORY. Check that the volume is mounted, then recreate the container again.
  • Leaving the variable set afterwards is harmless.
This covers local storage only. R2 addresses come from CLOUDFLARE_BUCKET_URL and do not change with FRONTEND_URL.

Cloudflare R2

Cloudflare R2 walks through the bucket, the token, the three choices for CLOUDFLARE_BUCKET_URL and the CORS policy. Any of those choices gives a public HTTPS address, so the networks that fetch by URL work.

How an upload travels

By default the browser uploads straight to the bucket, in parts, with addresses the backend signs for it. The file never passes through your server. With UPLOAD_VIA_SERVER=true, the browser sends the file to the backend, which writes it to the bucket.
The browser reports blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present, while the backend’s own calls (create-multipart-upload, sign-part) succeed. Usually the bucket answered 403 because something between the browser and R2 changed the signed request, such as a privacy extension stripping query parameters or a corporate proxy. R2 sends no CORS headers on errors, so the browser shows a CORS message instead. Check the bucket’s CORS policy first; if it is right, set UPLOAD_VIA_SERVER=true.

Video processing

Every uploaded video is converted in the background before it can be published: H.264 video and AAC audio in MP4, at most 1080p, turned upright, with the index at the start of the file and a poster frame. That is why a MOV from a phone works everywhere. The upload answers at once with the status processing, and the media becomes ready when the conversion is done. The work runs in the orchestrator, on its own media queue, with the ffmpeg that ships in the image. A file ffmpeg cannot read fails with an error on the media item and is not retried.
Last modified on September 23, 2026