Files
projectsend/docker/production/compose.example.yaml
T
ignacionelson d11bda094b Say out loud when scanning has quietly stopped protecting anything
The defaults let files through when the scanner cannot answer, so an
installation whose scanner died looks, from every screen anybody uses,
exactly like one that is working. Three places now say otherwise.

`projectsend:status` gains a `scanning` block: whether it is on, whether
it is managed, whether the scanner answers right now, the engine and
how old its definitions are, what is waiting, what is quarantined, and
how many files went out unscanned in the last 24 hours. Absent, null and
zero stay distinct — `reachable: null` means there is nothing to reach,
`false` means it should be answering and is not. The scans queue is
reported beside the other two.

The dashboard's System card carries the same warning for whoever is
actually looking at a screen, and says nothing at all while scanning is
healthy or switched off.

Docker gets the scanner as an opt-in profile — `--profile scanner` — in
both the development compose file and the published example, with a
clamd.conf whose Alert* options are what make an encrypted archive come
back as "could not scan" instead of "OK". No published ports: clamd has
no authentication and the file crosses that socket in the clear. Both
images also run a worker for the scans queue.

The dashboard test caught a 500 before it shipped: a nullable return
written as `array`.
2026-09-16 14:39:06 -03:00

142 lines
5.2 KiB
YAML

# A complete ProjectSend install using the official image.
#
# 1. Edit the passwords and APP_URL below.
# 2. docker compose -f compose.example.yaml up -d
# 3. Open APP_URL — the setup screen creates your administrator account.
#
# This is the file the Docker Hub description points at, so it is written
# for someone who has never seen the project before.
name: projectsend
services:
app:
image: projectsend/projectsend:2
restart: unless-stopped
ports:
# Put a TLS-terminating proxy in front of this in any real install.
# ProjectSend issues download links and password-reset emails using
# APP_URL, so that value — not this port — is what users must reach.
#
# Bound to the loopback address, not to every interface, because
# TRUSTED_PROXIES below is "*". That setting tells the application to
# believe the X-Forwarded-For header of whoever connects to it, which
# is correct behind a proxy and catastrophic when anybody can connect
# directly: a visitor who reaches this port themselves is then the
# "proxy", and can hand the application any client IP they like —
# which is enough to walk straight through the login lockout, every
# named rate limit, and the address recorded in the download log.
#
# Publishing on the loopback address keeps the proxy (on this host,
# or in this compose file) able to reach it while nothing off the
# machine can. If you move the proxy to another host, publish on the
# interface it comes from and narrow TRUSTED_PROXIES to that address
# or subnet at the same time — the two settings only make sense
# together.
- "127.0.0.1:8080:80"
environment:
APP_URL: https://files.example.com
APP_ENV: production
APP_DEBUG: "false"
# Generated on first boot and kept on the storage volume. Set it
# explicitly if you manage secrets elsewhere — but never change it on
# a running install: it decrypts existing data.
# APP_KEY: base64:...
DB_CONNECTION: mysql
DB_HOST: db
DB_PORT: "3306"
DB_DATABASE: projectsend
DB_USERNAME: projectsend
DB_PASSWORD: change-me-database
REDIS_HOST: redis
CACHE_STORE: redis
SESSION_DRIVER: redis
QUEUE_CONNECTION: redis
# Mail is easier to configure from System → Settings → Email once you
# are logged in — it has a "send test" button. These are the fallback
# until then.
MAIL_MAILER: smtp
MAIL_HOST: smtp.example.com
MAIL_PORT: "587"
MAIL_USERNAME: ""
MAIL_PASSWORD: ""
MAIL_FROM_ADDRESS: files@example.com
# Required whenever anything sits between your visitors and this
# container — which includes the reverse proxy you should be running.
# Without it every visitor appears to come from the proxy: the login
# rate limiter treats all of your users as one attacker, and the
# download log records the proxy's address.
#
# "*" means "trust whoever connects to me", which is only safe when
# nothing but the proxy can — which is what the loopback binding
# above is for. Change one and you have to change the other.
TRUSTED_PROXIES: "*"
# Optional: uncomment these — with a password of your own — to create
# the first administrator unattended and skip the setup screen. Left
# commented, the setup screen creates it instead. Ignored once any
# user exists.
# ADMIN_NAME: Administrator
# ADMIN_EMAIL: admin@example.com
# ADMIN_PASSWORD: change-me-admin
volumes:
# Every uploaded file lives here, along with the generated APP_KEY.
# This is the volume to back up; losing it loses the data.
- storage:/var/www/html/storage
depends_on:
db:
condition: service_healthy
db:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_DATABASE: projectsend
MYSQL_USER: projectsend
MYSQL_PASSWORD: change-me-database
MYSQL_ROOT_PASSWORD: change-me-root
volumes:
- db-data:/var/lib/mysql
healthcheck:
# The app waits for this before migrating, so a slow first start is
# normal rather than a failure.
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
interval: 5s
timeout: 5s
retries: 20
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis-data:/data
# Virus scanning, off unless you ask for it:
# docker compose --profile scanner up -d
# then point Settings → Virus scanning at tcp://clamav:3310.
#
# Budget about 1-1.5 GB of memory: the virus definitions are held in
# memory. The first start downloads them and does not answer until it
# has, which the Test button on that screen reports plainly.
clamav:
image: clamav/clamav:stable
restart: unless-stopped
# Deliberately no ports. clamd has no authentication and no
# encryption, so anything that can reach it can use it, and files
# cross that connection in the clear.
volumes:
- clamav-data:/var/lib/clamav
profiles:
- scanner
volumes:
storage:
db-data:
redis-data:
clamav-data: