# Verified compose for Nextcloud, tested 2026-10-18 by the SelfHostBench lab (3 runs; the figures are the median). # Lab host: AMD EPYC 7763 64-Core Processor, 2 vCPU, 7.8 GiB RAM, x86_64, Docker 28.0.4. # Floating image tags below were replaced by the version resolved at test time; # the registry digest is in the comment on each image line. # Measurements: nextcloud.json (schema 3). # Companion env file: nextcloud.env (save it as .env next to this file). # Secrets are not published: the lab's values are public. In nextcloud.env: # NEXTCLOUD_ADMIN_PASSWORD is commented out. Uncomment it and set your own value (the comment above it explains how). # POSTGRES_PASSWORD is commented out. Uncomment it and set your own value (the comment above it explains how). # Nextcloud (official image, apache variant) with PostgreSQL, Redis and a cron container. # Follows the examples in https://github.com/nextcloud/docker (.examples/docker-compose/insecure/postgres/apache): # the app and cron containers share one volume, and cron is the same image started with /cron.sh. # Differences from that example, all marked below: Postgres 18 and its volume path, healthchecks, # required passwords, and the first install done from environment variables (no web wizard). # There is no TLS in this file. Put a reverse proxy in front before you expose it (see the article). name: nextcloud services: db: # The admin manual lists PostgreSQL 14 to 18 for Nextcloud 35 (recommended: 18). # https://docs.nextcloud.com/server/35/admin_manual/installation/system_requirements.html image: postgres:18-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873 # version 18.6 (from exec); no registry tag for it could be verified, so the digest is pinned instead restart: unless-stopped environment: POSTGRES_DB: nextcloud POSTGRES_USER: nextcloud POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}" volumes: # The Nextcloud database. PostgreSQL 18 images keep data under /var/lib/postgresql (not .../data). # Back it up with pg_dump AND pg_dumpall --globals-only: the installer creates a second database # role (oc_admin) that a plain pg_dump does not contain. See the article. - db:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U nextcloud -d nextcloud"] interval: 5s timeout: 5s retries: 12 redis: # File locking and memory cache. No password and no published port: only the app and cron # containers on this Compose network can reach it. image: redis:alpine@sha256:3811787313eba226a2ef38658c6ccb91cd5e110edc89c37767de373120a0e5a0 # version 8.10.2 (from exec); no registry tag for it could be verified, so the digest is pinned instead restart: unless-stopped healthcheck: test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"] interval: 5s timeout: 3s retries: 12 app: image: nextcloud:35.0.2 # sha256:a588aa5353703b44106a6129adaaf7bf22521b7a4754f9f529b3228d6aa390c5 (version 35.0.2, from exec) restart: unless-stopped depends_on: db: condition: service_healthy redis: condition: service_healthy ports: # Localhost only. A reverse proxy on the host sends traffic here. - "127.0.0.1:8080:80" environment: # Database. With all of these set, the image installs Nextcloud on first start (no wizard). POSTGRES_HOST: db POSTGRES_DB: nextcloud POSTGRES_USER: nextcloud POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}" # First admin account. Used on the first start only; later changes here do nothing. NEXTCLOUD_ADMIN_USER: "${NEXTCLOUD_ADMIN_USER:-admin}" NEXTCLOUD_ADMIN_PASSWORD: "${NEXTCLOUD_ADMIN_PASSWORD:?set NEXTCLOUD_ADMIN_PASSWORD in .env}" # The host name you type in the browser. Nextcloud refuses requests for any other name. # Space-separated list. Added to config.php after the first install ("localhost" is there already). NEXTCLOUD_TRUSTED_DOMAINS: "${NEXTCLOUD_DOMAIN}" # Redis for file locking, memory cache and PHP sessions. Without this line the redis # container above runs but Nextcloud never connects to it. REDIS_HOST: redis # Behind a reverse proxy you will also need OVERWRITEPROTOCOL and TRUSTED_PROXIES. # They are not set here because this file has no proxy. See the article. volumes: # Nextcloud code, config.php and every user's files. This is your data: back it up. # The app and cron containers must mount the same volume. - nextcloud:/var/www/html healthcheck: # Passes when status.php reports the install finished (not just that Apache answers). test: ["CMD-SHELL", "curl -fsS http://localhost/status.php | grep -q '\"installed\":true'"] interval: 5s timeout: 5s retries: 60 start_period: 20s cron: # The same image, started as the background-job runner: busybox crond runs cron.php every 5 minutes. # After its first run Nextcloud switches the background-jobs mode from AJAX to Cron by itself. image: nextcloud:35.0.2 # sha256:a588aa5353703b44106a6129adaaf7bf22521b7a4754f9f529b3228d6aa390c5 (version 35.0.2, from exec) restart: unless-stopped entrypoint: /cron.sh depends_on: app: condition: service_healthy volumes: - nextcloud:/var/www/html volumes: db: nextcloud: