projectsend.org
← All posts

Running ProjectSend with Docker

How we'd set up ProjectSend 2 with Docker Compose, where your files and database end up, and how to back them up.

· By ProjectSend Team

A system administrator places a small house-shaped server on a shelf, next to separate boxes for the database, the files and a key.

Docker is the setup we recommend for ProjectSend 2, so we wanted to walk through it the way we'd explain it to someone setting it up for the first time. Nothing here is new: it's all in the Docker guide. It's just a lot to take in at once, so this is the short tour.

What you need

A server with Docker Engine and the Compose plugin. The containers bring PHP, MySQL, Redis and the background worker with them.

If you'd rather not use Docker, ProjectSend also runs on a regular PHP server (PHP 8.4+, MySQL 8.0+, nginx with PHP-FPM, and shell access). That one is covered in the install guide.

Starting it

Download the example compose file and save it as compose.yaml, which is the name docker compose looks for:

curl -o compose.yaml https://raw.githubusercontent.com/projectsend/projectsend/main/docker/production/compose.example.yaml

Edit it to set APP_URL (the address people will use) and the passwords, then start it:

docker compose up -d

When you open APP_URL, the setup screen asks you to create the first admin account.

If you're behind a reverse proxy

Most setups have something in front handling TLS: Nginx Proxy Manager, Traefik, Caddy or plain nginx. The example compose file sets TRUSTED_PROXIES: "*" and publishes the port on 127.0.0.1, so only the proxy can reach the container. If you write your own compose file, it's worth keeping both.

Worth knowing: without TRUSTED_PROXIES, ProjectSend doesn't know the proxy took care of TLS, builds http:// links while you're on https://, and the first form you submit fails with a 419 "page expired". If you ever see that, this is the first place to look. Your proxy also needs to pass the original Host header through, which most do by default.

Where your data lives

Two Docker volumes hold everything ProjectSend can't rebuild on its own:

  • the database (/var/lib/mysql): accounts, groups, permissions, share links, comments and the activity log;
  • storage (/var/www/html/storage): the uploaded files, and storage/.env, which holds your APP_KEY.

APP_KEY is easy to overlook. It's what decrypts the SMTP and LDAP passwords saved in the database, so a backup of the files without the key leaves those passwords unreadable. Backing up the whole storage directory covers both.

The System panel in the dashboard shows where your uploaded files are stored, and warns you if they're in a Docker volume or inside the container itself.

A note on docker compose down -v: the -v removes the named volumes, which on a real install means the database and the uploads. It's handy on a development machine and best avoided anywhere else. docker volume prune and docker system prune --volumes do the same while the stack is stopped.

Keeping the data in a folder you choose

We prefer bind-mounting both volumes to regular folders on the host, so the data is easy to find and your usual backup tool can pick it up:

sudo mkdir -p /srv/projectsend/storage /srv/projectsend/mysql

Then, in compose.yaml:

services:
  app:
    volumes:
      - /srv/projectsend/storage:/var/www/html/storage
  db:
    volumes:
      - /srv/projectsend/mysql:/var/lib/mysql

Mount the whole storage folder (not only storage/app/files) so APP_KEY comes along, and run docker compose config to check the result. If you already have data in the named volumes, the Docker guide explains how to move it.

Backups

For the database, a dump is safer than copying its files while it runs:

docker compose exec -T db sh -c \
    'mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" \
        --single-transaction --routines --triggers \
        projectsend' > projectsend-$(date +%F).sql

For the files and the key, rsync is enough:

rsync -a /srv/projectsend/storage/ /your/backup/location/storage/

If you can, try restoring once on another machine. It's the only way to know the backup works.

Updating

docker compose pull
docker compose up -d

The container runs the database migrations when it starts. Taking a dump before updating is still a good habit, since migrations only go forward.

Questions and corrections

The Docker guide goes further: virus scanning with ClamAV, debugging a 502 behind a proxy, moving to another server. If something here doesn't match what you're seeing, tell us on Discord or open an issue on GitHub. The docs live in the repository, so fixes are welcome as pull requests too.

Frequently asked questions

Where does ProjectSend keep its data when it runs in Docker?
In two volumes. The database lives at /var/lib/mysql, and storage at /var/www/html/storage holds the uploaded files plus storage/.env with your APP_KEY.
Why do I get a 419 "page expired" error behind a reverse proxy?
Usually because TRUSTED_PROXIES isn't set, so ProjectSend doesn't know the proxy handled TLS and builds http:// links while you're on https://. The example compose file sets it to "*".
What should I back up?
A database dump made with mysqldump --single-transaction, and the whole storage directory, so the uploaded files and APP_KEY are saved together.
How do I update ProjectSend in Docker?
Run docker compose pull and then docker compose up -d. The container runs the database migrations when it starts; taking a database dump first is a good habit.

ProjectSend Team. Written by the people who build and maintain ProjectSend, the open-source client portal developed in the open since 2011.

ProjectSend is free and open source

It's GPL v2 and built in the open. If something here didn't work for you, or you know a better way to do it, let us know: that's how the docs and the software get better.

Prefer not to run a server? ProjectSend Cloud is the hosted version, run by the same people.