feat: add nextcloud, seafile, opencloud, collabora and onlyoffice services

This commit is contained in:
tiennm99 committed 2026-10-11 03:17:10 +07:00
1 parent 9259273668
commit 51dea06fdd
16 files changed
+713

No files matched your search

+5
View File
@@ -79,6 +79,7 @@ Each links to its own README for variables, ports, and storage.
| [alloy](alloy/README.md) | Grafana Alloy shipping host and Docker telemetry to Grafana Cloud |
| [code-server](code-server/README.md) | VS Code in the browser from the official image |
| [code-server-lsio](code-server-lsio/README.md) | VS Code in the browser from the LinuxServer image, as a remote dev box |
| [collabora](collabora/README.md) | Collabora Online CODE, an online office suite for WOPI hosts |
| [couchbase](couchbase/README.md) | Couchbase Server |
| [diun](diun/README.md) | Image-update notifier, reading the Docker API through a read-only proxy |
| [gitea](gitea/README.md) | Gitea backed by PostgreSQL |
@@ -86,10 +87,14 @@ Each links to its own README for variables, ports, and storage.
| [goclaw](goclaw/README.md) | Multi-tenant AI agent gateway, with pgvector PostgreSQL |
| [hermes](hermes/README.md) | Hermes Agent with its built-in web dashboard |
| [litellm](litellm/README.md) | LiteLLM proxy in front of many LLM providers, with PostgreSQL and Redis |
| [nextcloud](nextcloud/README.md) | Nextcloud file sync and share, with PostgreSQL, Redis and a cron container |
| [onlyoffice](onlyoffice/README.md) | ONLYOFFICE Docs Community Edition, an online document editor |
| [open-webui](open-webui/README.md) | Open WebUI chat interface for OpenAI-compatible and Ollama providers |
| [openclaw](openclaw/README.md) | OpenClaw AI agent gateway, with built-in browser automation |
| [opencloud](opencloud/README.md) | OpenCloud file sync and share, single container with built-in identity provider |
| [owncloud](owncloud/README.md) | ownCloud file sync and share, with MariaDB and Redis |
| [paseo](paseo/README.md) | Paseo coding-agent daemon and web UI |
| [seafile](seafile/README.md) | Seafile CE file sync and share, with MariaDB, Redis and the notification server |
| [traffmonetizer](traffmonetizer/README.md) | TraffMonetizer bandwidth-sharing client |
| [webtop](webtop/README.md) | Ubuntu XFCE desktop in the browser |
+18
View File
@@ -0,0 +1,18 @@
# Copy to .env and fill in. Never commit .env.
#
# cp .env.example .env
# WOPI host allowed to open documents, as scheme://host:port. Further
# comma-separated entries are aliases of the same host.
COLLABORA_ALIASGROUP1=https://cloud.example.com:443
# Admin console login, at /browser/dist/admin/admin.html.
# Generate a password with: openssl rand -base64 24
COLLABORA_ADMIN_USERNAME=admin
COLLABORA_ADMIN_PASSWORD=
# Public hostname, without scheme.
COLLABORA_SERVER_NAME=office.example.com
# Spell-check languages loaded at start.
# COLLABORA_DICTIONARIES=de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru
+82
View File
@@ -0,0 +1,82 @@
# collabora
[Collabora Online](https://www.collaboraonline.com/code/) Development Edition
(CODE): an online office suite for documents, spreadsheets and presentations.
It holds no files itself; a WOPI host, a file server with Collabora
integration, opens documents in it.
One container: `collabora`.
## Setup
1. Set `COLLABORA_ALIASGROUP1` to the WOPI host's URL,
`COLLABORA_ADMIN_PASSWORD` and `COLLABORA_SERVER_NAME`.
2. Map the domain to port `9980` and deploy.
3. In the WOPI host, set the Collabora server URL to the public `https://`
address of this domain.
Discovery: `GET /hosting/discovery` returns XML once the server is up. The
image ships its own health check (`coolwsd --probe`, against `/livez`), so the
compose file declares none.
WebSocket editing sessions go through Traefik's default routing; no extra
labels are needed.
## Environment
| Variable | Default | Purpose |
| --- | --- | --- |
| `COLLABORA_ALIASGROUP1` | — | Allowed WOPI host, `scheme://host:port`; later comma-separated entries are aliases of it |
| `COLLABORA_ADMIN_USERNAME` / `COLLABORA_ADMIN_PASSWORD` | `admin` / — | Admin console at `/browser/dist/admin/admin.html` |
| `COLLABORA_SERVER_NAME` | empty | Public hostname; empty derives it from each request |
| `COLLABORA_DICTIONARIES` | optional | Space-separated spell-check languages; upstream's default list when unset |
`aliasgroup1` is the only access control on document editing: with no group
set, CODE trusts whichever host connects first after each start. Entries are
regular expressions, so `https://.*\.example\.com:443` allows a whole domain.
A second, unrelated WOPI host needs its own `aliasgroup2` line in
`compose.yml`.
`extra_params` is fixed in `compose.yml`: `--o:ssl.enable=false` serves plain
HTTP on `9980` for Traefik to terminate TLS in front of it, and
`--o:ssl.termination=true` makes CODE build `https://` and `wss://` URLs
anyway. Without it the editor loads over HTTPS but fails on mixed-content
WebSocket URLs.
The admin password fails fast if unset, because the console is served on the
public domain.
## Storage
None. Documents stay on the WOPI host; CODE's jails and cache are rebuilt on
every start.
## Isolation
CODE isolates each document process in a jail, choosing the first that works:
a mount namespace, then a chroot built by `coolforkit-caps`, then landlock.
The compose file adds no capabilities and no `security_opt`. Docker's default
seccomp profile blocks the `unshare` a mount namespace needs, so CODE falls back
to the chroot. `coolforkit-caps` carries file capabilities `CAP_CHOWN`,
`CAP_FOWNER` and `CAP_SYS_CHROOT`, all in Docker's default set, so it works
unprivileged. `MKNOD`, which older CODE images needed, is no longer used.
The price is speed, not safety: without `CAP_SYS_ADMIN` the `coolmount` helper
cannot bind-mount the system template, so each new jail copies it, and
opening a document takes a little longer. The alternatives cost more:
`cap_add: SYS_ADMIN` grants the container broad host-kernel powers, and
upstream's `cool-seccomp-profile.json`, Docker's default list plus the six
namespace and mount syscalls needed, must exist as a file on the host, which a Coolify compose deploy does
not place there.
The startup log line `creating usernamespace for mount user failed` is this
fallback, not a fault. Do not set `no-new-privileges`: `coolforkit-caps` gains
its capabilities on exec, and without them CODE drops to the weaker landlock
jail.
## Image
`collabora/code:latest` is the only moving tag upstream publishes; releases are
versioned `YY.MM.x`. The container keeps no state, so a new release needs only
a redeploy.
+11
View File
@@ -0,0 +1,11 @@
services:
collabora:
image: collabora/code:latest
restart: unless-stopped
environment:
- aliasgroup1=${COLLABORA_ALIASGROUP1:?required}
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
- username=${COLLABORA_ADMIN_USERNAME:-admin}
- password=${COLLABORA_ADMIN_PASSWORD:?required}
- server_name=${COLLABORA_SERVER_NAME:-}
# - dictionaries=${COLLABORA_DICTIONARIES:-de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru}
+23
View File
@@ -0,0 +1,23 @@
# Copy to .env and fill in. Never commit .env.
#
# cp .env.example .env
# Admin account, created on first start only.
# Generate a password with: openssl rand -base64 24
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=
# Public hostname, without scheme. The only trusted domain and the CLI URL.
NEXTCLOUD_DOMAIN=nextcloud.example.com
# Space-separated proxy addresses or CIDRs allowed to set X-Forwarded-* headers.
TRUSTED_PROXIES=10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# PostgreSQL credentials, used by the database and by the first-start install.
POSTGRES_DB=nextcloud
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD=
# PHP limits for the web server.
PHP_MEMORY_LIMIT=1G
PHP_UPLOAD_LIMIT=16G
+85
View File
@@ -0,0 +1,85 @@
# nextcloud
[Nextcloud](https://github.com/nextcloud/docker) Server: file sync and share,
calendar, contacts and office apps, with web, desktop and mobile clients.
## Containers
| Service | Image | Internal port | Role |
| --- | --- | --- | --- |
| `nextcloud` | `nextcloud:35-apache` | 80 | Web app |
| `cron` | `nextcloud:35-apache` | none | Background jobs, via `/cron.sh` |
| `db` | `postgres:18-alpine` | 5432 | Metadata, users and shares |
| `cache` | `redis:8-alpine` | 6379 | File locking and the distributed cache |
In Coolify, give `nextcloud` the domain from `NEXTCLOUD_DOMAIN` on port `80`.
`nextcloud` and `cron` wait for `db` and `cache` to pass their health checks.
## Setup
1. Set `NEXTCLOUD_DOMAIN`, `NEXTCLOUD_ADMIN_PASSWORD` and `POSTGRES_PASSWORD`.
2. Map the domain to port `80` and deploy. The first start installs Nextcloud
and creates the admin account.
3. Log in with `NEXTCLOUD_ADMIN_USER` / `NEXTCLOUD_ADMIN_PASSWORD`. The first
run of `cron` switches background jobs to **Cron** on its own.
Office editing is an app from the app store plus an external document server,
both set up in the admin UI, not in this compose file.
## Variables
| Variable | Default | Purpose |
| --- | --- | --- |
| `NEXTCLOUD_ADMIN_USER` / `NEXTCLOUD_ADMIN_PASSWORD` | `admin` / — | Admin account |
| `NEXTCLOUD_DOMAIN` | — | Public hostname, without scheme |
| `TRUSTED_PROXIES` | private IPv4 ranges | Proxies allowed to set `X-Forwarded-*` |
| `POSTGRES_DB` / `POSTGRES_USER` / `POSTGRES_PASSWORD` | `nextcloud` / `nextcloud` / — | Database credentials, read by `db` and by the install |
| `PHP_MEMORY_LIMIT` | `1G` | PHP `memory_limit` |
| `PHP_UPLOAD_LIMIT` | `16G` | PHP `upload_max_filesize` and `post_max_size` |
`NEXTCLOUD_DOMAIN` fills `NEXTCLOUD_TRUSTED_DOMAINS` and `OVERWRITECLIURL`.
Nextcloud rejects requests for any host not on the trusted list.
The admin and database variables are read only by the first-start install.
The installer creates its own database role and writes it to `config.php`, so
changing them later changes nothing; use the web UI or `occ`.
## Storage
| Volume | Mount | Holds |
| --- | --- | --- |
| `nextcloud-html` | `/var/www/html` | Nextcloud code, apps, `config.php` and user files |
| `db-data` | `/var/lib/postgresql` | The database |
| `cache-data` | `/data` | Redis locks and cache |
`cron` mounts the same volume at the same path as `nextcloud`; the two must
match for background jobs to see the same installation.
The Redis contents are rebuilt after a restart, but the `redis` image declares
`/data` a volume, so without a named one Docker creates an anonymous volume on
every recreate.
## Choices
- **Behind a TLS-terminating proxy.** The proxy speaks HTTP to port 80, so
`OVERWRITEPROTOCOL=https` keeps generated links and redirects on HTTPS.
`APACHE_DISABLE_REWRITE_IP=1` with `TRUSTED_PROXIES` makes Nextcloud read
the client IP and host from `X-Forwarded-*`, which brute-force protection
and the admin overview's proxy check rely on. No port is published, so only
containers on the app's networks can reach port 80; the private ranges cover
whatever subnet the proxy network gets.
- **Larger PHP limits.** The image defaults both to `512M`. The web UI and
the desktop and mobile clients upload in chunks, but WebDAV clients that send
a file in one request hit `PHP_UPLOAD_LIMIT`, and preview generation for big
images needs more memory.
- **`cron` container.** Nextcloud's recommended background job mode is system
cron; the image ships `/cron.sh`, which runs `cron.php` as `www-data` every
five minutes.
- **`nextcloud:35-apache`.** The major tag takes point releases; Nextcloud
can only upgrade one major at a time, so a new major is a deliberate change.
The `apache` variant serves PHP itself, with no separate web server.
- **`postgres:18-alpine`** is the version the Nextcloud 35 admin manual
recommends. Postgres 18 images keep data under `/var/lib/postgresql/18/`,
so the volume mounts at `/var/lib/postgresql`. A new Postgres major cannot
read an older data directory without a dump and restore.
- **`redis:8-alpine`** matches the Redis line upstream's example uses.
+72
View File
@@ -0,0 +1,72 @@
services:
nextcloud:
image: nextcloud:35-apache
restart: unless-stopped
environment:
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:?required}
- NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN:?required}
- OVERWRITEPROTOCOL=https
- OVERWRITECLIURL=https://${NEXTCLOUD_DOMAIN:?required}
- TRUSTED_PROXIES=${TRUSTED_PROXIES:-10.0.0.0/8 172.16.0.0/12 192.168.0.0/16}
- APACHE_DISABLE_REWRITE_IP=1
- POSTGRES_HOST=db
- POSTGRES_DB=${POSTGRES_DB:-nextcloud}
- POSTGRES_USER=${POSTGRES_USER:-nextcloud}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?required}
- REDIS_HOST=cache
- PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-1G}
- PHP_UPLOAD_LIMIT=${PHP_UPLOAD_LIMIT:-16G}
volumes:
- nextcloud-html:/var/www/html
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
# Runs Nextcloud background jobs every five minutes.
cron:
image: nextcloud:35-apache
restart: unless-stopped
entrypoint: /cron.sh
volumes:
- nextcloud-html:/var/www/html
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
# PostgreSQL for file metadata, users and shares.
db:
image: postgres:18-alpine
restart: unless-stopped
environment:
- POSTGRES_DB=${POSTGRES_DB:-nextcloud}
- POSTGRES_USER=${POSTGRES_USER:-nextcloud}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?required}
volumes:
- db-data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
# Redis for file locking and the distributed cache.
cache:
image: redis:8-alpine
restart: unless-stopped
volumes:
- cache-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10
volumes:
nextcloud-html:
db-data:
cache-data:
+10
View File
@@ -0,0 +1,10 @@
# Copy to .env and fill in. Never commit .env.
#
# cp .env.example .env
# Shared secret for signing editor requests; the storage app must use the same.
# Generate one with: openssl rand -hex 32
JWT_SECRET=
# Allow downloads from and callbacks to private IP addresses.
# ALLOW_PRIVATE_IP_ADDRESS=false
+60
View File
@@ -0,0 +1,60 @@
# onlyoffice
[ONLYOFFICE Docs](https://github.com/ONLYOFFICE/Docker-DocumentServer)
Community Edition: an online editor for documents, spreadsheets and
presentations. It has no file storage or user accounts of its own; a storage
app opens files in it and receives the saved result.
One container, `onlyoffice`. Map the domain to port `80`.
## Setup
1. Set `JWT_SECRET`.
2. Map the domain to port `80` and deploy. Every start regenerates the font
list, so the health check takes a minute or two to pass.
3. Give the storage app the public URL and the same `JWT_SECRET`.
Health check: `GET /healthcheck`. It answers `200` with `false` when a
dependency is down, so the compose healthcheck matches the body `true` rather
than relying on the status code.
## Environment
| Variable | Default | Purpose |
| --- | --- | --- |
| `JWT_SECRET` | — | Secret for signing requests between the editor and the storage app |
| `ALLOW_PRIVATE_IP_ADDRESS` | `false` | Allow fetching files from, and calling back to, private IP addresses |
`JWT_ENABLED` is fixed to `true`. `JWT_SECRET` fails fast if unset: the image
otherwise generates a random secret on every start, which breaks the storage
app's connection after each redeploy.
`ALLOW_PRIVATE_IP_ADDRESS` is needed only when the storage app is reached over
a private network, such as a container hostname or a LAN address. The editor
refuses those addresses by default, so leave it off when the storage app uses
a public URL.
## Storage
| Volume | Mount | Holds |
| --- | --- | --- |
| `onlyoffice-data` | `/var/www/onlyoffice/Data` | Certificates and generated WOPI keys |
| `onlyoffice-lib` | `/var/lib/onlyoffice` | Document cache and converted files |
| `onlyoffice-logs` | `/var/log/onlyoffice` | Logs |
Nothing here is the documents themselves; those stay in the storage app. The
volumes keep the cache and keys across redeploys.
## Images
`onlyoffice/documentserver:latest`: upstream publishes `X.Y` and `X.Y.Z` tags
but no major tag. Back up the volumes before a pull that crosses a major.
The 9.x images up to 9.4 still run PostgreSQL, RabbitMQ and Redis inside the
container for the Community Edition, in volumes the image declares itself;
upstream's source has since dropped them from the Community build. Either way
no separate database, broker or cache container is needed, and none of their
connection variables are set.
`stop_grace_period: 60s` follows upstream's compose, giving the editor time to
save open documents back to the storage app on shutdown.
+24
View File
@@ -0,0 +1,24 @@
services:
onlyoffice:
image: onlyoffice/documentserver:latest
restart: unless-stopped
stop_grace_period: 60s
environment:
- JWT_ENABLED=true
- JWT_SECRET=${JWT_SECRET:?required}
# - ALLOW_PRIVATE_IP_ADDRESS=${ALLOW_PRIVATE_IP_ADDRESS:-false}
volumes:
- onlyoffice-data:/var/www/onlyoffice/Data
- onlyoffice-lib:/var/lib/onlyoffice
- onlyoffice-logs:/var/log/onlyoffice
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost/healthcheck | grep -qx true"]
interval: 30s
timeout: 10s
retries: 5
start_period: 120s
volumes:
onlyoffice-data:
onlyoffice-lib:
onlyoffice-logs:
+13
View File
@@ -0,0 +1,13 @@
# Copy to .env and fill in. Never commit .env.
#
# cp .env.example .env
# Public hostname, without scheme.
OC_DOMAIN=opencloud.example.com
# Password of the built-in admin account, set on first start only.
# Generate one with: openssl rand -base64 24
INITIAL_ADMIN_PASSWORD=
# OC_LOG_LEVEL=info
# PROXY_ENABLE_BASIC_AUTH=false
+70
View File
@@ -0,0 +1,70 @@
# opencloud
[OpenCloud](https://docs.opencloud.eu/) Server: file sync and share with web,
desktop and mobile clients, spaces and WebDAV.
One container, `opencloud`, running every OpenCloud service in one process,
including the built-in identity provider, user directory and NATS. Files and
metadata live on disk, so there is no database container.
## Setup
1. Set `OC_DOMAIN` and `INITIAL_ADMIN_PASSWORD`.
2. Map the domain to port `9200` over HTTPS and deploy.
3. Log in as `admin` with `INITIAL_ADMIN_PASSWORD`.
Health check: `GET http://127.0.0.1:9205/healthz`, the proxy's debug endpoint,
also the compose healthcheck. It listens on loopback only.
## Environment
| Variable | Default | Purpose |
| --- | --- | --- |
| `OC_DOMAIN` | — | Public hostname, without scheme; becomes `OC_URL` |
| `INITIAL_ADMIN_PASSWORD` | — | Password of the `admin` account |
| `OC_LOG_LEVEL` | `info` | Optional. Log level |
| `PROXY_ENABLE_BASIC_AUTH` | `false` | Optional. Basic auth for WebDAV clients without OpenID Connect |
`OC_URL` must be the exact HTTPS URL the browser uses: the built-in identity
provider uses it as its issuer and redirect target.
`INITIAL_ADMIN_PASSWORD` is read only on first start, when the admin account
is created. Changing it later does not change the password; use the web UI.
`PROXY_TLS=false`, `PROXY_HTTP_ADDR` and `OC_INSECURE=false` are fixed in
`compose.yml`. The platform proxy terminates TLS and forwards plain HTTP to
port `9200`, and the certificate it serves is a real one, so certificate
checks stay on.
## Storage
| Volume | Mount | Holds |
| --- | --- | --- |
| `opencloud-config` | `/etc/opencloud` | `opencloud.yaml`, with the generated secrets |
| `opencloud-data` | `/var/lib/opencloud` | User files, spaces, the user directory and search index |
The two volumes belong together. `opencloud.yaml` holds the secrets the data
was written with; a data volume restored without its config volume, or the
reverse, does not start cleanly. Back them up as a pair.
The image creates both directories owned by uid `1000`, the user it runs as,
so fresh named volumes are writable without an init step.
## Choices
- **`opencloud init || true; opencloud server`.** `init` writes
`opencloud.yaml` with random secrets on first start and fails harmlessly
once it exists, as in upstream's own compose.
- **No config files.** OpenCloud's built-in CSP and app list cover the web UI
and the built-in identity provider. Upstream's `csp.yaml` and `apps.yaml`
only add origins for an external identity provider, office server and
optional web apps.
- **No office integration.** Editing documents in the browser needs a
separate office server on its own domain, OpenCloud's collaboration service,
and a custom `csp.yaml` allowing that domain. The office server also needs
extra kernel capabilities and a WOPI proof key. None of that can be switched
on by variables alone, so it is left out.
- **`opencloudeu/opencloud:7`.** The `opencloud` repository carries the
production releases, and `7` tracks the 7.x line. The `opencloud-rolling`
repository, which upstream's compose defaults to, ships a new major every
few months.
+28
View File
@@ -0,0 +1,28 @@
services:
opencloud:
image: opencloudeu/opencloud:7
restart: unless-stopped
# Writes the config with generated secrets on first start, then runs the server.
entrypoint: ["/bin/sh"]
command: ["-c", "opencloud init || true; opencloud server"]
environment:
- OC_URL=https://${OC_DOMAIN:?required}
- IDM_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD:?required}
- PROXY_TLS=false
- PROXY_HTTP_ADDR=0.0.0.0:9200
- OC_INSECURE=false
# - OC_LOG_LEVEL=${OC_LOG_LEVEL:-info}
# - PROXY_ENABLE_BASIC_AUTH=${PROXY_ENABLE_BASIC_AUTH:-false}
volumes:
- opencloud-config:/etc/opencloud
- opencloud-data:/var/lib/opencloud
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:9205/healthz"]
interval: 30s
timeout: 5s
retries: 5
start_period: 60s
volumes:
opencloud-config:
opencloud-data:
+21
View File
@@ -0,0 +1,21 @@
# Copy to .env and fill in. Never commit .env.
#
# cp .env.example .env
# Public hostname, without scheme.
SEAFILE_SERVER_HOSTNAME=seafile.example.com
# Shared secret between Seafile and the notification server, 32+ characters.
# Generate one with: openssl rand -hex 32
JWT_PRIVATE_KEY=
# Admin account, created on first start only.
INIT_SEAFILE_ADMIN_EMAIL=admin@example.com
INIT_SEAFILE_ADMIN_PASSWORD=
# MariaDB passwords. Seafile uses the root password on first start to create
# the seafile user and its three databases.
DB_PASSWORD=
DB_ROOT_PASSWORD=
TIME_ZONE=Etc/UTC
+98
View File
@@ -0,0 +1,98 @@
# seafile
[Seafile](https://manual.seafile.com/13.0/) Community Edition 13: file sync
and share with libraries, web, desktop and mobile clients. Based on the
official 13.0 Docker compose, without its bundled Caddy; the platform proxy
terminates TLS.
Four containers: `seafile` (Seahub web UI and file server), `notification`
(real-time change notifications over WebSocket), `db` (MariaDB) and `cache`
(Redis).
## Setup
1. Set `SEAFILE_SERVER_HOSTNAME`, `JWT_PRIVATE_KEY`, `INIT_SEAFILE_ADMIN_EMAIL`,
`INIT_SEAFILE_ADMIN_PASSWORD`, `DB_PASSWORD` and `DB_ROOT_PASSWORD`.
2. Map the domains, both on the same host:
| Container | Domain | Port |
| --- | --- | --- |
| `seafile` | `https://seafile.example.com` | `80` |
| `notification` | `https://seafile.example.com/notification` | `8083` |
Keep the app's strip-prefix setting on (the default), so the notification
server receives `/` rather than `/notification`.
3. Deploy. The first start creates the databases and the admin account; log in
with `INIT_SEAFILE_ADMIN_EMAIL` / `INIT_SEAFILE_ADMIN_PASSWORD`.
Health check: `curl -f http://localhost:80` inside `seafile`, from the official
compose. Its start period is two minutes instead of the official ten seconds:
the first start creates the databases before Seahub answers, and
`notification` waits for `seafile` to be healthy.
## Environment
| Variable | Default | Purpose |
| --- | --- | --- |
| `SEAFILE_SERVER_HOSTNAME` | — | Public hostname, without scheme; also builds the notification URL |
| `JWT_PRIVATE_KEY` | — | Shared secret between `seafile` and `notification`, 32+ characters |
| `INIT_SEAFILE_ADMIN_EMAIL` / `INIT_SEAFILE_ADMIN_PASSWORD` | — | Admin account, first start only |
| `DB_PASSWORD` | — | Password of the `seafile` MariaDB user |
| `DB_ROOT_PASSWORD` | — | MariaDB root password, used by the first start to create the user and databases |
| `TIME_ZONE` | `Etc/UTC` | Server time zone |
`SEAFILE_SERVER_PROTOCOL` is fixed to `https`: TLS ends at the proxy, but the
generated links, CSRF origins and notification URL must use the public scheme.
The `INIT_*` variables and `DB_ROOT_PASSWORD` take effect only on the first
start. Changing them later does not change the admin account or the database
passwords.
Logs go to stdout (`SEAFILE_LOG_TO_STDOUT=true`) so they show in the
platform's log view instead of only under `/shared/seafile/logs`.
## Storage
| Volume | Mount | Holds |
| --- | --- | --- |
| `seafile-data` | `/shared` | Libraries, config, logs |
| `db-data` | `/var/lib/mysql` | The ccnet, seafile and seahub databases |
| `cache-data` | `/data` | Redis cache |
`notification` has no volume: it reads its settings from the environment and,
with `SEAFILE_LOG_TO_STDOUT=true`, writes no log file, so the log mount of the
official compose is not needed.
The `redis` image declares `/data` a volume, so without a named one Docker
creates an anonymous volume on every recreate.
## Left out
- **SeaDoc** (`ENABLE_SEADOC=false`). It needs `/sdoc-server/` with the prefix
stripped and `/socket.io/` with it kept, on the same host. The proxy's
strip-prefix setting applies to the whole app, so both cannot be routed at
once.
- **Thumbnail server.** It needs `/thumbnail/` unstripped and
`/thumbnail/ping` rewritten to `/ping`, the same conflict. Without it, Seahub
generates thumbnails itself.
- **SeaSearch, Seafile AI, metadata server.** Not needed for file sync;
SeaSearch publishes no arm64 image.
- **Redis password.** The official compose passes an empty one by default;
Redis is reachable only on the app's internal network.
## Images
`seafileltd/seafile-mc:13.0-latest` and
`seafileltd/notification-server:13.0-latest` track the 13.0 line. Seafile
publishes no `:13` tag, `latest` has not moved since 2025, and 14 is still a
testing release. A Seafile major upgrade runs database migrations; back up
both volumes first.
`mariadb:10.11` is the version the official 13.0 compose pins, and the tag
follows its patch releases. `MARIADB_AUTO_UPGRADE=1` (from the official compose) runs
`mariadb-upgrade` after a minor update. A MariaDB data directory cannot move
back to an older major.
`redis:8` is the major the official compose's unpinned `redis` resolves to
today; the tag keeps it there instead of following a future major with no
review. Redis holds only cache, so a version change loses nothing.
+93
View File
@@ -0,0 +1,93 @@
services:
seafile:
image: seafileltd/seafile-mc:13.0-latest
restart: unless-stopped
environment:
- SEAFILE_SERVER_HOSTNAME=${SEAFILE_SERVER_HOSTNAME:?required}
- SEAFILE_SERVER_PROTOCOL=https
- JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?required}
- INIT_SEAFILE_ADMIN_EMAIL=${INIT_SEAFILE_ADMIN_EMAIL:?required}
- INIT_SEAFILE_ADMIN_PASSWORD=${INIT_SEAFILE_ADMIN_PASSWORD:?required}
- SEAFILE_MYSQL_DB_HOST=db
- SEAFILE_MYSQL_DB_PORT=3306
- SEAFILE_MYSQL_DB_USER=seafile
- SEAFILE_MYSQL_DB_PASSWORD=${DB_PASSWORD:?required}
- INIT_SEAFILE_MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:?required}
- SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
- SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
- SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db
- CACHE_PROVIDER=redis
- REDIS_HOST=cache
- REDIS_PORT=6379
- ENABLE_NOTIFICATION_SERVER=true
- INNER_NOTIFICATION_SERVER_URL=http://notification:8083
- NOTIFICATION_SERVER_URL=https://${SEAFILE_SERVER_HOSTNAME:?required}/notification
- ENABLE_SEADOC=false
- TIME_ZONE=${TIME_ZONE:-Etc/UTC}
- SEAFILE_LOG_TO_STDOUT=true
volumes:
- seafile-data:/shared
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 2m
# Notification server: pushes library changes to web and desktop clients over WebSocket.
notification:
image: seafileltd/notification-server:13.0-latest
restart: unless-stopped
environment:
- JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?required}
- SEAFILE_MYSQL_DB_HOST=db
- SEAFILE_MYSQL_DB_PORT=3306
- SEAFILE_MYSQL_DB_USER=seafile
- SEAFILE_MYSQL_DB_PASSWORD=${DB_PASSWORD:?required}
- SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
- SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
- SEAFILE_LOG_TO_STDOUT=true
depends_on:
db:
condition: service_healthy
seafile:
condition: service_healthy
# MariaDB for the ccnet, seafile and seahub databases.
db:
image: mariadb:10.11
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:?required}
- MYSQL_LOG_CONSOLE=true
- MARIADB_AUTO_UPGRADE=1
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--mariadbupgrade", "--innodb_initialized"]
interval: 20s
start_period: 30s
timeout: 5s
retries: 10
# Redis for the Seahub cache.
cache:
image: redis:8
restart: unless-stopped
volumes:
- cache-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
seafile-data:
db-data:
cache-data: