Skip to main content

How files are stored

Everything an admin uploads, from gallery photos to rehearsal tracks, is kept outside the database in file storage. This page explains how that storage is arranged and covers the default, the server's own disk. It is for whoever installs the server and decides where files live.

Two stores​

The site keeps files in two separate stores, because they need different protection.

StoreWhat goes in itWho can fetch a file
PublicGallery photos and videos, concert posters, past-event posters, the logo and any picture uploaded into Site Settings.Anyone. These are on your public pages anyway.
PrivateDocuments shared with members, and rehearsal tracks.Nobody directly. Only the site reads this store, and it sends a file on only after checking that the person asking is a logged-in member or an admin.

The private store must never be given a public address, whatever provider you use. The site does not hand out links into it: a member's browser asks the site for a file, the site checks the login, and then passes the file through. It does so in pieces when asked, so audio can be skipped forwards and backwards and plays in Safari.

Keys, not addresses​

Each stored file has a key: a folder, the time of upload, a random part and the original extension, such as gallery/1767225600000-3f9a…c2.jpg. The original file name is not part of it.

For gallery items, past-event posters uploaded on the Past Events page, member files and rehearsal tracks, the database records the key, and the address is worked out each time a page is served. Those keep working if you later change where public files are served from.

Concert posters, the logo and pictures in Site Settings are different: uploading one fills the field with an address, and that address is what is saved. With the default settings it is a path on your own site, such as /files/posters/1767225600000-3f9a…c2.jpg, which goes on working as long as the file is in the store. With PUBLIC_FILES_URL set it is a full address on that other domain, and it is not rewritten if you change the setting afterwards.

Folder in the keyStoreHolds
gallery/publicGallery photos and videos
posters/publicConcert posters
past-events/publicPosters uploaded on the Past Events page
images/publicThe logo and other pictures uploaded into settings
member-files/privateDocuments shared with members
rehearsal-tracks/privateRehearsal tracks

Choosing a provider​

STORAGE_PROVIDER chooses where both stores are kept.

PlatformSTORAGE_PROVIDERWhere files goPage
Node, Dockerlocal (the default)A folder on the server's diskthis page
Node, Dockers3Two buckets on any S3-compatible serviceS3-compatible storage
Cloudflare Workersunset, or anything but s3Two R2 buckets bound to the WorkerR2 on Cloudflare
Cloudflare Workerss3Two buckets on any S3-compatible serviceS3-compatible storage

On Node the value must be written exactly, in lower case. Anything else stops the server at start:

Unknown STORAGE_PROVIDER "…". Use local or s3 (r2 is for Cloudflare).

Local disk is not available on Cloudflare, and the R2 bindings are not available on Node. A Node server can still use R2 through its S3 interface.

Use local when the site is one server with a disk you back up. Use s3 when the disk does not last (many platform services), when there is more than one copy of the site, or when you would rather the files were on a storage service.

Local disk​

VariableWhat it doesDefault
STORAGE_PROVIDERlocal keeps files on the server's disk.local on Node
STORAGE_LOCAL_DIRThe folder to keep them in../data/storage

Inside that folder the site makes public and private, and inside each a folder for every kind of file as it is first needed:

data/storage/
public/
gallery/
1767225600000-3f9a…c2.jpg
1767225600000-3f9a…c2.jpg.meta.json
posters/
images/
private/
member-files/
rehearsal-tracks/

Beside every file is a small .meta.json holding the file's type, which the site reads when it sends the file. If one goes missing the site falls back on the file's extension, so the pair should be kept together but losing a .meta.json is not fatal.

Where the folder is​

A relative STORAGE_LOCAL_DIR is taken from the folder the server is started in, as with the SQLite file. Set an absolute path, or make sure the working directory is always the same.

In the Docker image, docker-compose.sqlite.yml sets STORAGE_LOCAL_DIR=/app/data/storage on the data volume. Without a volume on /app/data, every upload is lost when the container is replaced: Docker: HTTPS, ports and volumes.

Permissions​

The account the site runs as needs to read and write the whole folder. Nobody else needs any access, and the private half holds things only members should see:

sudo chown -R choir:choir /opt/choirmaster/data/storage
sudo chmod -R u=rwX,go= /opt/choirmaster/data/storage

Here choir stands for your service account.

Do not point a web server at this folder to serve files directly. Pointing one at storage or at private would publish members' files without any login. The site serves public files itself at /files/.

Space and backups​

Local storage has no limit of its own. It grows with what is uploaded, and a gallery of videos at up to 100 MB each fills a small disk quickly. Watch the free space.

The folder is not copied by anything in the site, including the safety copy made before an update, which covers the database only. Back it up together with the database: Back up a single-server site.

Moving between providers​

There is no tool that copies files from one provider to another.

Because keys are the same whatever the provider, the files can in principle be copied across with the storage service's own tools, keeping each key as it is: everything under public/ into the public bucket and everything under private/ into the private one, leaving out the .meta.json files and setting each object's content type. That path follows from how the site is built, but it is not a documented procedure and was not tested for this guide. Try it on a copy first, and remember that concert posters and settings pictures are saved as addresses, as described above.

If something goes wrong​

What you seeCause
An upload in the admin panel says Upload failed, and the log has EACCES or ENOSPCThe service account cannot write to the folder, or the disk is full.
Pictures uploaded before a restart or a move are brokenA relative STORAGE_LOCAL_DIR with a different working directory, or in Docker no volume on /app/data.
A file's address answers File not foundThe file is not in the store: it was deleted there, or the store was changed without copying the files.