Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,24 @@ updates:
update-types:
- minor
- patch

# Docker images in the admin-manual compose example. Only literal image tags
# are updated here (owncloud/server uses ${OWNCLOUD_IMAGE} interpolation, which
# Dependabot cannot resolve, so it is intentionally left for manual bumps).
- package-ecosystem: docker
directories:
- "/modules/admin_manual/examples/installation/docker"
# The compose smoke-test harness. It inherits the example's image tags, but
# is listed so any image pinned here in future is tracked too.
- "/tests/docker-compose"
schedule:
interval: weekly
day: sunday
time: '22:00'
timezone: Etc/UTC
open-pull-requests-limit: 5
groups:
minor-and-patch:
update-types:
- minor
- patch
32 changes: 32 additions & 0 deletions .github/workflows/compose-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: docker-compose example

# Lints and smoke-tests the ownCloud + Collabora CODE docker-compose example
# that ships in the admin manual. Runs only when the example, the test harness,
# or this workflow change.
on:
push:
branches:
- master
paths:
- 'modules/admin_manual/examples/installation/docker/**'
- 'tests/docker-compose/**'
- '.github/workflows/compose-test.yml'
pull_request:
branches:
- master
paths:
- 'modules/admin_manual/examples/installation/docker/**'
- 'tests/docker-compose/**'
- '.github/workflows/compose-test.yml'

jobs:
smoke-test:
name: Lint and smoke-test the compose example
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

# smoke-test.sh validates the merged compose config as its first action,
# then boots the stack and asserts on it.
- name: Boot the stack and run smoke tests
run: tests/docker-compose/smoke-test.sh
165 changes: 146 additions & 19 deletions modules/admin_manual/examples/installation/docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Fixes the Compose project name, so the generated resource names (containers,
# networks, volumes) do not depend on the directory the stack is launched from.
# The "frontend" network at the bottom therefore always becomes
# "owncloud_frontend", which is the name the traefik.docker.network label on the
# ownCloud service refers to. If you run several copies of this stack on one
# host, change this and update that label to match "<name>_frontend".
name: owncloud

# This oc-common template is a compose extension for ownCloud.
# It is used for starting the main ownCloud instance and reused e.g. when defining listeners for WND.
# This eases compose maintenance a lot.
Expand All @@ -14,7 +22,7 @@ x-owncloud: &oc-common
- OWNCLOUD_DB_TYPE=mysql
- OWNCLOUD_DB_NAME=owncloud
- OWNCLOUD_DB_USERNAME=owncloud
- OWNCLOUD_DB_PASSWORD=owncloud
- OWNCLOUD_DB_PASSWORD=${MYSQL_PASSWORD}
- OWNCLOUD_DB_HOST=mariadb
- OWNCLOUD_ADMIN_USERNAME=${ADMIN_USERNAME}
- OWNCLOUD_ADMIN_PASSWORD=${ADMIN_PASSWORD}
Expand All @@ -26,16 +34,35 @@ x-owncloud: &oc-common
source: oc_files
target: /mnt/data
networks:
- oc_network
# ownCloud needs both tiers: the proxy reaches it on "frontend", and it
# reaches MariaDB/Redis on "backend".
- frontend
- backend

services:

owncloud:
# Start the main ownCloud instance
<<: *oc-common
container_name: owncloud_server
ports:
- ${HTTP_PORT}:8080
# No ports are published directly. The Traefik reverse proxy (see the "proxy"
# service below) terminates TLS and forwards HTTPS traffic to port 8080.
labels:
- traefik.enable=true
- traefik.http.routers.owncloud.rule=Host(`${OWNCLOUD_DOMAIN}`)
- traefik.http.routers.owncloud.entrypoints=websecure
- traefik.http.routers.owncloud.tls.certresolver=letsencrypt
- traefik.http.services.owncloud.loadbalancer.server.port=8080
# ownCloud is attached to two networks (frontend + backend). Tell Traefik
# which one to use to reach it, otherwise it picks one at random and may
# choose "backend", which the proxy cannot reach — breaking all routing.
- traefik.docker.network=owncloud_frontend
# Enable HSTS on ownCloud only. Do NOT add frame-ancestors/X-Frame-Options
# here or on Collabora: ownCloud embeds the Collabora editor in an iframe and
# a restrictive framing policy would break document editing.
- traefik.http.routers.owncloud.middlewares=owncloud-hsts
- traefik.http.middlewares.owncloud-hsts.headers.stsSeconds=15552000
- traefik.http.middlewares.owncloud-hsts.headers.stsIncludeSubdomains=true

# owncloud-wnd-1:
# # Enterprise only: uncomment and configure it accordingly if WND is used !
Expand All @@ -45,35 +72,125 @@ services:
# container_name: owncloud-wnd-1
# command: ["/usr/bin/owncloud", "occ", "wnd:listen", "myhost", "myshare", "workgroup\myuser", "password"]

collabora:
# Collabora Online Development Edition (CODE): the self-hosted office server
# that ownCloud connects to as a WOPI client. See the "Enabling Collabora
# Online (CODE)" section of the documentation for the required occ wiring.
# Image tag pinned here (not in .env) so Dependabot keeps it current — see
# .github/dependabot.yml. Do not re-parameterize it: Dependabot cannot read
# ${VAR} interpolation.
image: collabora/code:24.04.13.2.1
container_name: owncloud_collabora
restart: always
environment:
# "domain" is a regex of the ownCloud host(s) CODE accepts as WOPI host.
# Dots must be escaped for the regex (e.g. owncloud\.example\.com).
- domain=${OWNCLOUD_DOMAIN_REGEX}
- aliasgroup1=https://${OWNCLOUD_DOMAIN}:443
- username=${COLLABORA_ADMIN_USERNAME}
- password=${COLLABORA_ADMIN_PASSWORD}
# TLS is terminated at the reverse proxy, so CODE itself speaks plain HTTP.
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
cap_add:
- MKNOD
labels:
- traefik.enable=true
# Public WOPI router: serves the document editor to ownCloud users.
- traefik.http.routers.collabora.rule=Host(`${COLLABORA_DOMAIN}`)
- traefik.http.routers.collabora.entrypoints=websecure
- traefik.http.routers.collabora.tls.certresolver=letsencrypt
- traefik.http.services.collabora.loadbalancer.server.port=9980
# Pinned low, so the admin router below always wins regardless of how long
# COLLABORA_DOMAIN is (see the note there).
- traefik.http.routers.collabora.priority=100
# Admin console router (higher priority): the CODE admin console and its
# WebSocket must NOT be reachable from the internet. Restrict them to
# trusted source ranges. WOPI clients never need these paths.
- traefik.http.routers.collabora-admin.rule=Host(`${COLLABORA_DOMAIN}`) && (PathPrefix(`/browser/dist/admincontrol`) || PathPrefix(`/cool/adminws`))
- traefik.http.routers.collabora-admin.entrypoints=websecure
- traefik.http.routers.collabora-admin.tls.certresolver=letsencrypt
# Priority must beat the "collabora" router above. Traefik auto-computes an
# unpinned router's priority from its rule-string length, so a long enough
# COLLABORA_DOMAIN would out-rank a small fixed number here and steal these
# paths — bypassing the allowlist. Pinning both routers removes the
# dependency on the domain length entirely.
- traefik.http.routers.collabora-admin.priority=200
- traefik.http.routers.collabora-admin.service=collabora
- traefik.http.routers.collabora-admin.middlewares=collabora-admin-allowlist
- traefik.http.middlewares.collabora-admin-allowlist.ipallowlist.sourcerange=127.0.0.1/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
networks:
- frontend

proxy:
# Traefik terminates TLS (Let's Encrypt) and routes the two hostnames to the
# ownCloud and Collabora containers. Routing/TLS is driven by the labels on
# each service above; WebSocket upgrades (needed by CODE) are handled
# transparently by Traefik.
# Pinned tag maintained by Dependabot (see .github/dependabot.yml).
# Must be >= v3.6: the Docker provider in earlier releases probes the daemon
# with API version 1.24 before negotiating, which Docker Engine >= 29 rejects
# ("client version 1.24 is too old"), leaving Traefik with no routing config.
image: traefik:v3.7
container_name: owncloud_proxy
restart: always
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# Redirect all plain HTTP traffic to HTTPS.
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}
- --certificatesresolvers.letsencrypt.acme.storage=/acme/acme.json
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
ports:
- "80:80"
- "443:443"
volumes:
# NOTE: mounting the Docker socket gives Traefik full control of the Docker
# daemon, which is equivalent to root on the host. The ":ro" flag only makes
# the socket file read-only; it does NOT restrict the Docker API. For a
# hardened setup, put a scoped docker-socket-proxy in front of it instead.
- /var/run/docker.sock:/var/run/docker.sock:ro
- acme:/acme
networks:
# The proxy only needs the frontend tier; it must not reach MariaDB/Redis.
- frontend

mariadb:
image: mariadb:${MARIADB_IMAGE}
# Pinned tag maintained by Dependabot (see .github/dependabot.yml).
image: mariadb:10.11
container_name: owncloud_mariadb
restart: always
ports:
- "3306:3306"
# No host ports are published. MariaDB is reached only by ownCloud over the
# internal "backend" network. Publishing 3306 to the host would expose the
# database to the internet on the public host this stack is designed for.
environment:
- MYSQL_ROOT_PASSWORD=owncloud
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_USER=owncloud
- MYSQL_PASSWORD=owncloud
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=owncloud
- MARIADB_AUTO_UPGRADE=1
command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-u", "root", "--password=owncloud"]
test: ["CMD", "mysqladmin", "ping", "-u", "root", "--password=${MYSQL_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- mysql:/var/lib/mysql
networks:
- oc_network
- backend

redis:
image: redis:${REDIS_IMAGE}
# Pinned tag maintained by Dependabot (see .github/dependabot.yml).
image: redis:7
container_name: owncloud_redis
restart: always
ports:
- "6379:6379"
# No host ports are published. Redis is reached only by ownCloud over the
# internal "backend" network. An unauthenticated Redis published to the host
# would be directly exploitable on a public server.
command: ["--databases", "1"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
Expand All @@ -83,7 +200,7 @@ services:
volumes:
- redis:/data
networks:
- oc_network
- backend

# As alternative to Redis, you can enable Memcached.
# See the caching configuration documentation for more details.
Expand All @@ -92,10 +209,8 @@ services:
# image: memcached:latest
# container_name: owncloud_memcached
# restart: always
# ports:
# - "11211:11211"
# networks:
# - oc_network
# - backend

volumes:
oc_files:
Expand All @@ -110,6 +225,18 @@ volumes:
# Use the above options when configuring bind mounts
redis:
driver: local
acme:
# Stores the Let's Encrypt certificates issued for the reverse proxy.
driver: local

networks:
oc_network:
# "frontend" carries public traffic (proxy <-> ownCloud/Collabora).
# "backend" is internal only (ownCloud <-> MariaDB/Redis) so the internet-facing
# proxy and Collabora cannot reach the data tier directly.
# These are project-scoped: the top-level "name: owncloud" above fixes the
# prefix, so they materialize as "owncloud_frontend" / "owncloud_backend".
# Traefik's traefik.docker.network label must name the *real* network; given a
# short name it warns and then silently falls back to the container's first
# network, which may be "backend" — unreachable from the proxy.
frontend:
backend:
32 changes: 26 additions & 6 deletions modules/admin_manual/examples/installation/docker/dot.env
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
# IMPORTANT: every credential below is a placeholder. Replace every CHANGE_ME_*
# value with a strong, unique secret before exposing this stack to the internet.
OWNCLOUD_IMAGE={latest-server-download-version}
OWNCLOUD_DOMAIN=localhost
OWNCLOUD_TRUSTED_DOMAINS=localhost
OWNCLOUD_OVERWRITE_CLI_URL=http://localhost
HTTP_PORT={std-port-http}
MARIADB_IMAGE=10.11
REDIS_IMAGE=7
# The public hostnames must resolve (DNS) to the host running this stack, so the
# reverse proxy can obtain Let's Encrypt certificates for them.
OWNCLOUD_DOMAIN=owncloud.example.com
OWNCLOUD_TRUSTED_DOMAINS=owncloud.example.com
OWNCLOUD_OVERWRITE_CLI_URL=https://owncloud.example.com

# Initial ownCloud web UI admin account (used for first setup only).
ADMIN_USERNAME=admin
ADMIN_PASSWORD=CHANGE_ME_ADMIN

# Database credentials. Use distinct, strong values for the root and app users.
MYSQL_ROOT_PASSWORD=CHANGE_ME_DB_ROOT
MYSQL_PASSWORD=CHANGE_ME_DB_APP

# Collabora Online Development Edition (CODE)
COLLABORA_DOMAIN=collabora.example.com
# Regex of the ownCloud host CODE accepts as WOPI host. Escape every dot with a
# backslash so it is not treated as a regex wildcard.
OWNCLOUD_DOMAIN_REGEX=owncloud\.example\.com
COLLABORA_ADMIN_USERNAME=admin
COLLABORA_ADMIN_PASSWORD=CHANGE_ME_COLLABORA

# Reverse proxy (Traefik) and Let's Encrypt
LETSENCRYPT_EMAIL=admin@example.com
Loading