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.comwith an A record pointing at203.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.
sudo mkdir -p /opt/nextcloud
cd /opt/nextcloudCreate the .env file that holds the database and Redis credentials:
sudo nano .envPaste this and replace every value with your own. Use long random strings for the passwords.
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.10Drop the TRUSTED_PROXIES line if nothing sits in front of the app. Then restrict the file:
sudo chmod 600 .envTip: 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:
sudo nano docker-compose.ymlPaste this:
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=ROWis the MariaDB configuration Nextcloud expects.- The
healthcheckblocks plusdepends_on: condition: service_healthystop the app from starting before the database accepts connections. See Docker Compose depends_on and healthchecks. - The named volumes
db_data,redis_dataandnextcloud_datakeep your data outside the containers, so recreating a container does not wipe anything. NEXTCLOUD_TRUSTED_DOMAINSis 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
sudo docker compose up -dThen watch the app come up:
sudo docker compose logs -f appWait until the log stops showing database connection retries, then check the containers:
sudo docker compose psAll 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:
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:
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_domainsBehind a reverse proxy, also set the overwrite values so links and redirects use the public URL:
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
- Open
http://203.0.113.10:8080in a browser. You should see the Nextcloud setup page. - Create the admin account and finish the wizard.
- Log in and open Settings → Overview. The setup checks should not complain about missing caches or a wrong database.
- Upload a file and confirm it appears in the file list.
- Confirm Redis is actually used:
sudo docker compose exec redis redis-cli -a change-this-redis-password info keyspaceYou should see at least one database with keys. An empty keyspace after browsing the web UI means the cache settings did not apply.
- Restart the stack and confirm your data survives:
sudo docker compose restartTroubleshooting
- The app container restarts in a loop with
SQLSTATE[HY000] [2002] Connection refused. The database is not ready, or the credentials in.envdo not match. Checkdocker compose logs dbfor startup errors and confirmMYSQL_USERandMYSQL_PASSWORDare identical for both services. - "Access through untrusted domain" when opening the site. Your hostname is missing from
trusted_domains. Add it with theocc config:system:set trusted_domainscommand above. - Redis errors in the Nextcloud log. The password in
redis.passwordmust matchREDIS_PASSWORDin.env. Test it withdocker compose exec redis redis-cli -a <password> ping, which should answerPONG. - Login works but pages are slow and the log shows file locking errors.
memcache.lockingis 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
