mirror of
https://github.com/tiennm99/composes.git
synced 2026-10-11 03:13:16 +00:00
feat: add nextcloud, seafile, opencloud, collabora and onlyoffice services
This commit is contained in:
1 parent
9259273668
commit
51dea06fdd
16 files changed
+713
No files matched your search
@@ -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 |
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
Reference in new issue
Block a user