Back to Article List

Nextcloud Docker Compose setup with MariaDB and Redis

Nextcloud Docker Compose setup with MariaDB and Redis

On any box that hosts other containers, I run Nextcloud from the plain nextcloud:apache image with my own Docker Compose file instead of AIO. AIO wants to be the whole show: it brings its own reverse proxy assumptions, its own Borg backups and a master container that manages the rest. Great when Nextcloud owns the machine, awkward when it's one service among eight on a shared Docker host. With a hand-written compose file, Nextcloud is just another stack that plays by the same rules as everything else on the server.

I've written up the full decision in the AIO vs manual install comparison, so here's the short version: pick AIO for a dedicated box where you want Talk and Collabora preconfigured, pick this compose setup when you want control and coexistence. The rest of this guide is the second path.

The Nextcloud Docker Compose file

This is the stack I'd deploy today on a Docker VPS: the official image from hub.docker.com/_/nextcloud, MariaDB 11.4 (the recommended release), Redis for locking and caching, and a fourth container that handles background jobs. Save it as compose.yaml in its own directory:

services:
  db:
    image: mariadb:11.4
    restart: unless-stopped
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=change-me-root
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=change-me

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  app:
    image: nextcloud:34-apache
    restart: unless-stopped
    ports:
      - "8080:80"
    depends_on:
      - db
      - redis
    volumes:
      - nextcloud:/var/www/html
    environment:
      - MYSQL_HOST=db
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=change-me
      - REDIS_HOST=redis
      - NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com

  cron:
    image: nextcloud:34-apache
    restart: unless-stopped
    entrypoint: /cron.sh
    depends_on:
      - db
      - redis
    volumes:
      - nextcloud:/var/www/html

volumes:
  db:
  nextcloud:

A few choices worth explaining. The image tag pins the major (34-apache), so a routine docker compose pull can never jump you across a major release by accident. The MYSQL_* variables appear twice on purpose: the db container uses them to create the database, the app container uses them to find it. REDIS_HOST=redis is all the app needs to wire up Redis for file locking and distributed caching, and NEXTCLOUD_TRUSTED_DOMAINS takes a space-separated list if you serve more than one hostname. MariaDB gets the READ COMMITTED isolation level and row-based binlog on the command line, which is what Nextcloud expects and saves you a config file mount.

I use named volumes rather than bind mounts because Docker manages permissions for them correctly out of the box, and a chown fight with a bind-mounted webroot is a rite of passage nobody needs twice. If you want the passwords out of the file, move them into a .env next to the compose file and reference them as ${MYSQL_PASSWORD}; compose reads it automatically, and the yaml stops being a secret you can't commit anywhere.

First start and watching the logs

Bring it up and follow the app container while it installs:

docker compose up -d
docker compose logs -f app

The first start unpacks Nextcloud into the nextcloud volume and takes a minute or two. When the log settles down, open port 8080, create the admin account and pick MySQL/MariaDB as the database (it's prefilled from the environment). If you'd rather script that step, the image also accepts NEXTCLOUD_ADMIN_USER and NEXTCLOUD_ADMIN_PASSWORD and installs unattended. Any occ command runs through compose exec:

docker compose exec -u www-data app php occ status

That should report installed: true with version 34.0.3 or newer. While you're there, confirm the Redis wiring took, because a Nextcloud that silently fell back to database locking works and then chokes the first time two clients sync one folder:

docker compose exec -u www-data app php occ config:system:get memcache.locking

Expected output is \OC\Memcache\Redis. If the app container restarts in a loop instead of installing, nine times out of ten the MYSQL_PASSWORD values in the two services don't match, and docker compose logs db shows the refused connections to prove it.

Background jobs: the cron container

The fourth service is the part most first compose files miss. It runs the same image with the entrypoint switched to /cron.sh, which executes cron.php every five minutes against the shared volume. The pattern comes straight from the examples folder in the nextcloud/docker repository, and it means background jobs run on schedule with no crontab on the host. The lazier alternative, a host cron line calling docker exec -u www-data nextcloud-app-1 php cron.php, works too but breaks quietly when the container name changes. Why any of this matters, and how to tell when jobs silently stop, is its own topic: see the Nextcloud cron and background jobs guide.

Reverse proxy settings for Nextcloud in Docker

Port 8080 straight to the internet is fine for a first test and wrong for production. In practice this stack sits behind Caddy, Traefik or an nginx on the host that terminates TLS. I won't turn this into a proxy tutorial, but two settings decide if the result works or generates redirect loops and wrong share links. Nextcloud has to trust the proxy's IP (trusted_proxies, settable via the image's TRUSTED_PROXIES variable) and has to know the outside world speaks HTTPS (overwriteprotocol set to https, or the OVERWRITEPROTOCOL variable). Get the first one wrong and every client shows up in the logs as the proxy's IP; get the second wrong and logins bounce between http and https forever. For a proxy running on the same Docker host I set TRUSTED_PROXIES=172.16.0.0/12, which covers Docker's default bridge networks without chasing the exact container IP after every restart. Generated links pointing at port 8080 are the remaining symptom to watch for, cured by setting overwrite.cli.url to the public https address. The full parameter list lives in the reverse proxy configuration docs.

Once the stack is reachable from outside, the host deserves the same attention as the app. I run CrowdSec beside stacks like this one, in the same compose workflow, and the setup is in the CrowdSec Docker Compose guide.

Upgrading the Nextcloud image

Minor updates are boring, which is the point:

docker compose pull
docker compose up -d

The image detects the version change on start and runs the upgrade before serving traffic, so expect a maintenance page for a few minutes on bigger jumps. Watch docker compose logs -f app during that window; the migration steps scroll by, and if one fails you want to know which one rather than staring at a stuck maintenance screen. I take a snapshot or a fresh backup before pulling anything, since the upgrade rewrites the database schema and there's no downgrade path.

Majors are the part to respect. When 35 goes stable, edit the tag from 34-apache to 35-apache in both the app and cron services, then pull and up. One major at a time, always; the updater refuses skips, and an image jump from 33 to 35 leaves you restoring from backup. I learned the one-major rule the hard way on my bare-metal instance during the 32 upgrade, and it cost me a Sunday. Docker makes the mechanics easier, the rule stays the same.

Where the data lives and how to back it up

Everything is in the two named volumes: nextcloud holds the application, config.php and user files (under /var/www/html/data inside the container), db holds MariaDB. On the host they sit under /var/lib/docker/volumes/. A usable backup is a dump plus a copy of the app volume, roughly:

docker compose exec db mariadb-dump -u nextcloud -pchange-me nextcloud > nextcloud-db.sql
sudo tar -czf nextcloud-files.tar.gz -C /var/lib/docker/volumes/nextcloud_nextcloud/_data .

The volume prefix depends on your directory name, so check docker volume ls before trusting that path. Dumps taken while clients are writing can be slightly inconsistent, and there's a maintenance-mode dance that fixes that, plus retention and offsite copies. All of it is in the Nextcloud backup and restore guide, which applies to this compose stack almost unchanged.

The pleasant side effect of this layout is portability. The compose file plus the two volumes is the entire instance, so moving to a bigger server means copying three things and running docker compose up -d on the new machine. I've done that migration twice now, and both times the longest step was waiting for the data transfer, with total downtime measured in the time it took DNS to catch up.

One last habit: after any change to the compose file, run docker compose config before up. It costs a second and catches the indentation typo that would otherwise take down a working instance at the worst possible moment.

Your idea deserves better hosting

24/7 support 30-day money-back guarantee Cancel anytime
Ciclo de Pagamento

VPS.S1

$5.99 Save  17 %
$4.99 por mês
  • 2 vCPU AMD EPYC
  • 2 GB RAMMEMÓRIA
  • 30 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos

VPS.S3

$14.99 Save  33 %
$9.99 por mês
  • 4 vCPU AMD EPYC
  • 6 GB RAMMEMÓRIA
  • 70 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O

EPYC VPS.P1

$8.99 Save  22 %
$6.99 por mês
  • 2 vCPU AMD EPYC
  • 4 GB RAMMEMÓRIA
  • 40 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

EPYC VPS.P2

$16.99 Save  24 %
$12.99 por mês
  • 2 vCPU AMD EPYC
  • 8 GB RAMMEMÓRIA
  • 80 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

EPYC VPS.P4

$29.99 Save  23 %
$22.99 por mês
  • 4 vCPU AMD EPYC
  • 16 GB RAMMEMÓRIA
  • 160 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

EPYC VPS.P5

$39.99 Save  25 %
$29.99 por mês
  • 8 vCPU AMD EPYC
  • 16 GB RAMMEMÓRIA
  • 180 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

EPYC VPS.P6

$59.99 Save  25 %
$44.99 por mês
  • 8 vCPU AMD EPYC
  • 32 GB RAMMEMÓRIA
  • 200 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

EPYC VPS.P7

$69.99 Save  29 %
$49.99 por mês
  • 16 vCPU AMD EPYC
  • 32 GB RAMMEMÓRIA
  • 240 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

Genoa VPS.G2

$24.99 Save  20 %
$19.99 por mês
  • 2 vCPUAMD EPYC Genoa 4ª geração 9xx4 com 3,25 GHz ou similar, na arquitetura Zen 4. AMD EPYC G4
  • 4 GB DDR5MEMÓRIA
  • 50 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

Genoa VPS.G4

$44.99 Save  22 %
$34.99 por mês
  • 4 vCPUProcessador AMD EPYC com núcleos vCPU dedicados, em hardware de servidor empresarial. AMD EPYC G4
  • 8 GB DDR5MEMÓRIA
  • 100 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

Genoa VPS.G6

$89.99 Save  22 %
$69.99 por mês
  • 8 vCPUProcessador AMD EPYC com núcleos vCPU dedicados, em hardware de servidor empresarial. AMD EPYC G4
  • 16 GB DDR5MEMÓRIA
  • 200 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

Genoa VPS.G7

$159.99 Save  22 %
$124.99 por mês
  • 8 vCPUProcessador AMD EPYC com núcleos vCPU dedicados, em hardware de servidor empresarial. AMD EPYC G4
  • 32 GB DDR5MEMÓRIA
  • 250 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6O suporte a IPv6 está indisponível no momento na França, Finlândia ou Países Baixos. incluídos
  • Backup automático grátisInclui um espaço de backup que você pode configurar para diário, semanal ou mensal.

AMD Ryzen VPS.R1

$16.99 Save  18 %
$13.99 por mês
  • 1 CPU dedicada AMD Ryzen 9 7950X com 4,5 GHz ou similar, na arquitetura Zen 4. vCPU
  • 4 GB DDR5MEMÓRIA
  • 50 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6 incluídos O suporte a IPv6 está indisponível no momento na França, Finlândia ou nos Países Baixos.
  • Backup automático incluso

AMD Ryzen VPS.R2

$29.99 Save  17 %
$24.99 por mês
  • 2 CPUs dedicadas AMD Ryzen 9 7950X com 4,5 GHz ou similar, na arquitetura Zen 4. vCPU
  • 8 GB DDR5MEMÓRIA
  • 100 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6 incluídos O suporte a IPv6 está indisponível no momento na França, Finlândia ou nos Países Baixos.
  • Backup automático incluso

AMD Ryzen VPS.R4

$109.99 Save  18 %
$89.99 por mês
  • 8 CPUs dedicadas AMD Ryzen 9 7950X com 4,5 GHz ou similar, na arquitetura Zen 4. vCPU
  • 32 GB DDR5MEMÓRIA
  • 400 GB NVMeDISCO
  • Banda ilimitada
  • IPv4 & IPv6 incluídos O suporte a IPv6 está indisponível no momento na França, Finlândia ou nos Países Baixos.
  • Backup automático incluso

Other questions

Can I use PostgreSQL instead of MariaDB in this compose file?

Yes. Swap the db service for a postgres image and replace the MYSQL_* variables with POSTGRES_HOST, POSTGRES_DB, POSTGRES_USER and POSTGRES_PASSWORD, which the official image supports the same way. Postgres 14 through 18 is supported. For a small instance I stay on MariaDB because most guides and defaults assume it.

GPU products are in high demand at the moment. Fill the form to get notified as soon as your preferred GPU server is back in stock.