Nextcloud Docker Compose: Production-Ready Setup with PostgreSQL & Redis (2026)
Build an enterprise-grade private cloud with Nextcloud in Docker. Includes PostgreSQL 16, Redis transactional locking, SSL reverse proxy, and backup scripts.
Nextcloud Docker Compose: Production-Ready Setup with PostgreSQL & Redis (2026)
Executive Summary & Architecture Overview
Nextcloud Hub is the undisputed champion of private self-hosted collaboration suites, delivering file synchronization, office document editing, encrypted messaging, and calendar management. However, the standard SQLite single-container setup frequently leads to database locking errors (database is locked), slow sync performance, and catastrophic corruption under multi-user loads.
A true production-ready homelab deployment requires three decoupled services working in harmony:
- Nextcloud Application Container: The core PHP-FPM / Apache web application.
- PostgreSQL 16 Database: High-concurrency relational backend database.
- Redis 7 In-Memory Cache: Dedicated transactional file locking and session memory cache.
+------------------------------------------------------------------------+
| HTTPS Traffic (Port 443) |
+-----------------------------------^------------------------------------+
|
+-----------------------------------v------------------------------------+
| Reverse Proxy (Nginx Proxy Manager / Traefik) |
+-----------------------------------^------------------------------------+
| Internal Proxy Bridge
+-----------------------------------v------------------------------------+
| Nextcloud App Container (nextcloud:apache) |
| - PHP 8.2+ OPCache enabled |
| - Background Cron Job container |
+-------------------^--------------------------------^-------------------+
| |
(SQL Queries)| (Locks / Memory Cache)
+-------------------v----------+ +-----------------v-------------------+
| PostgreSQL 16 Database | | Redis 7 Cache Container |
| - ACID Compliant Storage | | - Transactional File Locking |
| - UTF-8 Native Collations | | - Zero Disk IO for File Locks |
+------------------------------+ +-------------------------------------+
Hardware, OS & Network Requirements
| Specification | Minimum (1-3 Users) | Production Recommended (Homelab Family / Team) |
|---|---|---|
| CPU | 2 Cores (x86_64) | 4+ Cores (Modern Intel/AMD) |
| RAM | 4 GB | 8 GB - 16 GB |
| Storage (System) | 30 GB SSD for OS & DB | 100 GB NVMe SSD for PostgreSQL & DB indexes |
| Storage (Data) | 500 GB HDD | Multi-Terabyte ZFS / Btrfs mirror with automated scrubbing |
| Ports Required | Port 8080 (Internal HTTP proxy target) | Port 443 (External HTTPS via Reverse Proxy) |
Step 1: Host Directory Layout & Permissions
Create a robust directory hierarchy for database files, application configurations, and bulk user data:
sudo mkdir -p /opt/nextcloud/{html,data,db_data,redis_data} && \
cd /opt/nextcloud && \
sudo chown -R 33:33 /opt/nextcloud/html /opt/nextcloud/data && \
sudo chmod 750 /opt/nextcloud/data
Important: User ID
33:33corresponds towww-datainside the Debian-based official Nextcloud Docker image. Setting this ownership prevents initial setup permission faults.
Generate strong random credentials for your environment file:
cat << 'EOF' > /opt/nextcloud/.env
# PostgreSQL Configuration
POSTGRES_DB=nextcloud
POSTGRES_USER=nc_admin
POSTGRES_PASSWORD=f8a7e2b6d19c043e8a5b7c1d2e3f4a5b6c7d8e9f01a2b3c4
POSTGRES_HOST=nextcloud-db
# Redis Authentication Secret
REDIS_PASSWORD=9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f
# Nextcloud Admin Initial Setup
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=change_this_master_password_immediately
# Hostname and Data paths
NEXTCLOUD_TRUSTED_DOMAINS=cloud.yourhomelab.net 192.168.1.100
OVERWRITEHOST=cloud.yourhomelab.net
OVERWRITEPROTOCOL=https
OVERWRITECLIURL=https://cloud.yourhomelab.net
EOF
Step 2: Production-Grade docker compose.yaml
Save the following configuration as /opt/nextcloud/compose.yaml:
services:
nextcloud-db:
image: postgres:16-alpine
container_name: nextcloud-db
restart: unless-stopped
volumes:
- /opt/nextcloud/db_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=\${POSTGRES_DB}
- POSTGRES_USER=\${POSTGRES_USER}
- POSTGRES_PASSWORD=\${POSTGRES_PASSWORD}
networks:
- nextcloud-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER} -d \${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
nextcloud-redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: unless-stopped
command: redis-server --requirepass \${REDIS_PASSWORD}
volumes:
- /opt/nextcloud/redis_data:/data
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "redis-cli", "-a", "\${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
nextcloud-app:
image: nextcloud:apache
container_name: nextcloud-app
restart: unless-stopped
ports:
- "127.0.0.1:8080:80"
volumes:
- /opt/nextcloud/html:/var/www/html
- /opt/nextcloud/data:/var/www/html/data
environment:
- POSTGRES_HOST=nextcloud-db
- POSTGRES_DB=\${POSTGRES_DB}
- POSTGRES_USER=\${POSTGRES_USER}
- POSTGRES_PASSWORD=\${POSTGRES_PASSWORD}
- REDIS_HOST=nextcloud-redis
- REDIS_HOST_PASSWORD=\${REDIS_PASSWORD}
- NEXTCLOUD_ADMIN_USER=\${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD=\${NEXTCLOUD_ADMIN_PASSWORD}
- NEXTCLOUD_TRUSTED_DOMAINS=\${NEXTCLOUD_TRUSTED_DOMAINS}
- OVERWRITEHOST=\${OVERWRITEHOST}
- OVERWRITEPROTOCOL=\${OVERWRITEPROTOCOL}
- OVERWRITECLIURL=\${OVERWRITECLIURL}
depends_on:
nextcloud-db:
condition: service_healthy
nextcloud-redis:
condition: service_healthy
networks:
- nextcloud-net
nextcloud-cron:
image: nextcloud:apache
container_name: nextcloud-cron
restart: unless-stopped
volumes:
- /opt/nextcloud/html:/var/www/html
- /opt/nextcloud/data:/var/www/html/data
entrypoint: /cron.sh
depends_on:
- nextcloud-app
networks:
- nextcloud-net
networks:
nextcloud-net:
name: nextcloud-net
driver: bridge
Step 3: Deployment & High-Performance Optimization
Start your stack:
cd /opt/nextcloud && docker compose up -d
Monitor initial table initialization:
docker compose logs -f nextcloud-app
Once installed, apply high-performance memory cache and locking settings via Nextcloud's built-in occ CLI:
# Enable Redis Transactional File Locking
docker exec -u www-data nextcloud-app php occ config:system:set filelocking.enabled --value="true" --type=boolean
docker exec -u www-data nextcloud-app php occ config:system:set memcache.locking --value="\OC\Memcache\Redis"
docker exec -u www-data nextcloud-app php occ config:system:set memcache.local --value="\OC\Memcache\APCu"
docker exec -u www-data nextcloud-app php occ config:system:set memcache.distributed --value="\OC\Memcache\Redis"
# Add missing database indexes
docker exec -u www-data nextcloud-app php occ db:add-missing-indices
# Add missing bigints
docker exec -u www-data nextcloud-app php occ db:add-missing-primary-keys
Step 4: Reverse Proxy & Security Headers
In Nginx Proxy Manager or Nginx reverse proxy configuration, map cloud.yourhomelab.net to http://127.0.0.1:8080.
Add these essential Nextcloud headers to pass the Nextcloud Security & Setup Warnings check:
location /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
location /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
client_max_body_size 16G;
proxy_buffering off;
proxy_request_buffering off;
Step 5: Zero-Trust Remote Access with Tailscale
To sync files from mobile phones and remote laptops without opening port 80/443 to the whole world:
- Run Tailscale on your host:
sudo tailscale up. - Add your Tailscale IP or MagicDNS domain to trusted domains:
docker exec -u www-data nextcloud-app php occ config:system:set trusted_domains 2 --value="your-server.tailnet-xyz.ts.net"
- Your mobile Nextcloud app can now connect directly to
https://your-server.tailnet-xyz.ts.netvia your private wireguard mesh.
Step 6: Automated Backup & Disaster Recovery
Create /opt/nextcloud/backup.sh:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_PATH="/mnt/backups/nextcloud"
DATE=$(date +"%Y%m%d_%H%M%S")
mkdir -p "\${BACKUP_PATH}"
echo "[1/4] Turning on Nextcloud Maintenance Mode..."
docker exec -u www-data nextcloud-app php occ maintenance:mode --on
echo "[2/4] Dumping PostgreSQL Database..."
docker exec nextcloud-db pg_dump -U nc_admin nextcloud | gzip > "\${BACKUP_PATH}/nextcloud_db_\${DATE}.sql.gz"
echo "[3/4] Backing up Configuration & App Data..."
tar -czf "\${BACKUP_PATH}/nextcloud_config_\${DATE}.tar.gz" -C /opt/nextcloud html .env
echo "[4/4] Turning off Maintenance Mode..."
docker exec -u www-data nextcloud-app php occ maintenance:mode --off
# Delete backups older than 14 days
find "\${BACKUP_PATH}" -type f -name "*.gz" -mtime +14 -delete
echo "[✓] Backup successfully created at \${BACKUP_PATH}"
Schedule daily at 2:00 AM:
chmod +x /opt/nextcloud/backup.sh && \
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/nextcloud/backup.sh >> /var/log/nc-backup.log 2>&1") | crontab -
Step 7: Deep Troubleshooting Matrix
| Error / Symptom | Root Cause | Verified Resolution |
|---|---|---|
Access through untrusted domain |
Domain name not registered in config.php trusted domains. |
Run docker exec -u www-data nextcloud-app php occ config:system:set trusted_domains 1 --value="YOUR_DOMAIN". |
Strict-Transport-Security HTTP header is not set to at least 15552000 seconds |
Reverse proxy SSL configuration missing HSTS response header. | In Nginx Proxy Manager, check the HSTS Enabled box in the SSL tab. |
Background jobs show Some jobs haven't been executed since... |
Background jobs set to AJAX instead of Cron. | Go to Admin Settings -> Basic Settings -> Background jobs -> Select Cron (Recommended). Our nextcloud-cron container executes this automatically. |
Redis server went away |
Redis authentication password missing or invalid in Nextcloud config. | Verify REDIS_HOST_PASSWORD in .env and verify connection: docker exec -it nextcloud-redis redis-cli -a YOUR_PASS ping. |
| File uploads fail with large ISOs/videos | Upload limits restricted by PHP defaults or Nginx proxy buffers. | In reverse proxy add client_max_body_size 16G; and set docker exec -u www-data nextcloud-app php occ config:system:set max_chunk_size --value="20971520". |
Step 8: Frequently Asked Questions (FAQ)
1. Why should I use PostgreSQL instead of MariaDB?
PostgreSQL 16 handles concurrent locking, large file tables, and full-text search indexing with significantly fewer deadlocks and higher write throughput under intense synchronization loads.
2. How can I mount an external ZFS storage pool or NAS?
Enable the External storage support app in Nextcloud Apps. You can then mount NFS shares, SMB shares, or direct local bind mounts (/mnt/storage) with full read/write permissions.
3. How do I upgrade Nextcloud to future major versions?
Never jump multiple major versions. Change the image tag from nextcloud:29-apache to nextcloud:30-apache, run docker compose up -d, and then run docker exec -u www-data nextcloud-app php occ upgrade.
4. How do I integrate OnlyOffice or Collabora for document editing?
Add an additional container service (collabora/code or onlyoffice/documentserver) to the compose file and configure the connector app within Nextcloud admin settings.
5. Is Nextcloud AIO (All-In-One) better than custom Docker Compose?
Nextcloud AIO provides automatic container management via a master container. However, custom Docker Compose gives complete control over database tuning, backup paths, and resource limits preferred by experienced sysadmins.
Technical Questions & Inquiries
1Have a question about this guide or running into an error? Ask below — our technical support team usually replies in ~2 minutes.
What is the recommended Redis maxmemory-policy setting for Nextcloud transactional file locking in 2026?
Hi Marcus Vance,
Great question - this is a subtle but crucial setting for Nextcloud reliability. For transactional file locking, the only safe policy is
noeviction.Here's why: Nextcloud uses Redis for two distinct purposes - distributed locking (short-lived, must never be dropped) and caching (can be evicted). If Redis evicts a lock key under
allkeys-lruor similar, you'll get spurious "file is locked" errors or, worse, concurrent writes corrupting files.In your
docker-compose.yml, set it explicitly:Size it correctly: 512MB is a solid baseline for most homelabs. Monitor
redis-cli INFO memory- ifused_memoryapproaches your cap, increase it rather than switching policies.One exception: If you run a separate Redis instance for caching (e.g., via the Nextcloud
memcache.distributedconfig), that instance can useallkeys-lru. But for your transactional locking Redis - keepnoevictionlocked in.This remains the recommended approach through 2026 - Nextcloud's own docs and the core team still enforce this for data integrity. Pair it with
appendonly yesfor crash recovery, and you're solid.