Docker & DevOps

Self-host Nextcloud with Docker Compose, MariaDB and Redis

Run Nextcloud, MariaDB and Redis as containers on one Linux VPS. Build the compose file, set trusted domains and verify the stack end to end.

IntermediateLinux server5 min read

You can self-host Nextcloud with Docker Compose by running three containers: the Nextcloud app, a MariaDB database and Redis for caching and file locking. This guide builds that stack on a Linux VPS, connects the services with healthchecks so the app waits for the database, sets trusted domains and verifies the install end to end.

Before you start

You need:

  • A Linux VPS with root or sudo access and a public IPv4 address.
  • Docker Engine and the Compose plugin installed. Follow Install Docker and Docker Compose on a Linux VPS if you have not done that yet.
  • A domain such as cloud.example.com with an A record pointing at 203.0.113.10.
  • At least 2 GB of RAM and 20 GB of disk for a small instance. Nextcloud grows with the data you store.
  • SSH access to the server.

Note: This stack publishes the app on port 8080 and leaves TLS to a reverse proxy. If you plan to put Nginx Proxy Manager in front, read Nginx Proxy Manager in Docker: HTTPS for many self-hosted apps after this guide.

Step 1 — Create the project directory and.env

Work in /opt/nextcloud so the stack stays separate from other services.

Bash
sudo mkdir -p /opt/nextcloud
cd /opt/nextcloud

Create the .env file that holds the database and Redis credentials:

Bash
sudo nano .env

Paste this and replace every value with your own. Use long random strings for the passwords.

INI
MYSQL_ROOT_PASSWORD=change-this-root-password
MYSQL_DATABASE=nextcloud
MYSQL_USER=nextcloud
MYSQL_PASSWORD=change-this-db-password
REDIS_PASSWORD=change-this-redis-password
NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com 203.0.113.10
TRUSTED_PROXIES=203.0.113.10

Drop the TRUSTED_PROXIES line if nothing sits in front of the app. Then restrict the file:

Bash
sudo chmod 600 .env

Tip: A plain .env file works well, but Compose also supports Docker secrets. See Manage secrets and environment variables in Docker Compose if you want to go further.

Step 2 — Write the compose file

Create docker-compose.yml:

Bash
sudo nano docker-compose.yml

Paste this:

YAML
services:
  db:
    image: mariadb:11
    restart: unless-stopped
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MARIADB_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MARIADB_DATABASE: ${MYSQL_DATABASE}
      MARIADB_USER: ${MYSQL_USER}
      MARIADB_PASSWORD: ${MYSQL_PASSWORD}
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 10

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 10

  app:
    image: nextcloud:apache
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "8080:80"
    volumes:
      - nextcloud_data:/var/www/html
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      REDIS_HOST: redis
      REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
      NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS}
      TRUSTED_PROXIES: ${TRUSTED_PROXIES}
      PHP_MEMORY_LIMIT: 512M

volumes:
  db_data:
  redis_data:
  nextcloud_data:

The parts that matter:

  • --transaction-isolation=READ-COMMITTED --binlog-format=ROW is the MariaDB configuration Nextcloud expects.
  • The healthcheck blocks plus depends_on: condition: service_healthy stop the app from starting before the database accepts connections. See Docker Compose depends_on and healthchecks.
  • The named volumes db_data, redis_data and nextcloud_data keep your data outside the containers, so recreating a container does not wipe anything.
  • NEXTCLOUD_TRUSTED_DOMAINS is read on first start, when the config file is generated.

Warning: Never run docker compose down -v on this stack unless you intend to delete the database, the Nextcloud data and the Redis dump. The -v flag removes the named volumes.

Step 3 — Start the Nextcloud Docker Compose stack

Bash
sudo docker compose up -d

Then watch the app come up:

Bash
sudo docker compose logs -f app

Wait until the log stops showing database connection retries, then check the containers:

Bash
sudo docker compose ps

All three should be running, with db and redis reporting healthy.

Step 4 — Configure Redis caching and trusted domains

Nextcloud uses Redis for distributed caching and file locking. Run occ inside the app container as the web user:

Bash
sudo docker compose exec -u www-data app php occ config:system:set memcache.local --value="\OC\Memcache\APCu"
sudo docker compose exec -u www-data app php occ config:system:set memcache.distributed --value="\OC\Memcache\Redis"
sudo docker compose exec -u www-data app php occ config:system:set memcache.locking --value="\OC\Memcache\Redis"
sudo docker compose exec -u www-data app php occ config:system:set redis host --value="redis"
sudo docker compose exec -u www-data app php occ config:system:set redis port --value="6379"
sudo docker compose exec -u www-data app php occ config:system:set redis password --value="change-this-redis-password"

If you added a domain after the first start, add it to the trusted list:

Bash
sudo docker compose exec -u www-data app php occ config:system:set trusted_domains 1 --value="cloud.example.com"
sudo docker compose exec -u www-data app php occ config:system:get trusted_domains

Behind a reverse proxy, also set the overwrite values so links and redirects use the public URL:

Bash
sudo docker compose exec -u www-data app php occ config:system:set overwrite.cli.url --value="https://cloud.example.com"
sudo docker compose exec -u www-data app php occ config:system:set overwriteprotocol --value="https"

Check it works

  1. Open http://203.0.113.10:8080 in a browser. You should see the Nextcloud setup page.
  2. Create the admin account and finish the wizard.
  3. Log in and open Settings → Overview. The setup checks should not complain about missing caches or a wrong database.
  4. Upload a file and confirm it appears in the file list.
  5. Confirm Redis is actually used:
Bash
sudo docker compose exec redis redis-cli -a change-this-redis-password info keyspace

You should see at least one database with keys. An empty keyspace after browsing the web UI means the cache settings did not apply.

  1. Restart the stack and confirm your data survives:
Bash
sudo docker compose restart

Troubleshooting

  • The app container restarts in a loop with SQLSTATE[HY000] [2002] Connection refused. The database is not ready, or the credentials in .env do not match. Check docker compose logs db for startup errors and confirm MYSQL_USER and MYSQL_PASSWORD are identical for both services.
  • "Access through untrusted domain" when opening the site. Your hostname is missing from trusted_domains. Add it with the occ config:system:set trusted_domains command above.
  • Redis errors in the Nextcloud log. The password in redis.password must match REDIS_PASSWORD in .env. Test it with docker compose exec redis redis-cli -a <password> ping, which should answer PONG.
  • Login works but pages are slow and the log shows file locking errors. memcache.locking is not pointing at Redis. Re-run the cache commands in step 4.
  • MariaDB refuses to start after an upgrade with an authentication plugin error. See [Fix MariaDB
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.