When teams outgrow consumer cloud drives, they need three things: secure document sharing, predictable storage costs, and control over access policies. Nextcloud delivers all three — file sync, sharing, Office-compatible editing, calendars, and contacts — on infrastructure you own. This Nextcloud Docker setup guide walks you through a production deployment using Docker Compose, PostgreSQL, Redis, and a reverse proxy with automatic HTTPS. No vendor lock-in. No per-user fees. Just your data, on your server.
Related reads:
- Production Guide: Deploy Nextcloud with Docker Compose + Caddy + PostgreSQL on Ubuntu
- Production Guide: Deploy Nextcloud with Docker Compose + NGINX + MariaDB + Redis on Ubuntu
- Deploy Nextcloud with Docker Compose, Nginx, and Redis on Ubuntu (Production Guide)
Prerequisites
Before you start, make sure you have:
- Ubuntu 22.04 or 24.04 server with at least 2 vCPU, 4GB RAM, and 40GB+ SSD storage
- Docker Engine (v24+) and Docker Compose v2 installed
- A domain name with a DNS A record pointing to your server's IP
- Ports 80 and 443 open in your firewall
- A valid email address for Let's Encrypt notifications
- Basic familiarity with Docker, Compose, and reverse proxies
Verify your Docker installation:
docker --version
docker compose version
# Confirm ports are available
sudo ss -tlnp | grep -E ':80|:443'
Why Nextcloud and Why Docker?
Nextcloud is the most widely deployed open-source file sharing platform. It gives you a Google Drive-grade experience — sync, sharing, document editing, calendars, contacts — while keeping all data on your infrastructure. Teams reach for Nextcloud when they need data residency, compliance control, or simply want to stop paying per-seat fees as headcount grows.
Why Docker Compose?
- Isolated services — each component (app, database, cache, proxy) runs in its own container with clear boundaries.
- Persistent volumes — user files and database state survive container restarts and upgrades.
- Version pinning — pin image tags for reproducible deployments and safe rollbacks.
- Secret management — credentials live in environment files, not in the Compose file or version control.
- Operational clarity — start, stop, backup, and upgrade the entire stack with single commands.
The tradeoff is operational responsibility: you manage backups, updates, and capacity. For teams that value control over convenience, that's the point.
Step 1: Create the Project Directory and Environment File
Keep everything under /opt/nextcloud for predictable operations and backup paths.
sudo mkdir -p /opt/nextcloud && cd /opt/nextcloud
sudo chown -R $USER:$USER /opt/nextcloud
Create the secrets file with restricted permissions. Never commit this to version control:
cat > /opt/nextcloud/.env <<'EOF'
POSTGRES_DB=nextcloud
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD=ChangeMe_StrongPassword_42!
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=AnotherStrongPassword_99#
NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com
NC_DOMAIN=cloud.example.com
[email protected]
EOF
chmod 600 /opt/nextcloud/.env
Step 2: Deploy the Docker Compose Stack
This stack includes Nextcloud, PostgreSQL, Redis for caching and file locking, and Caddy for automatic HTTPS. Only Caddy exposes ports to the host.
cat > /opt/nextcloud/docker-compose.yml << 'EOF'
version: "3.9"
services:
db:
image: postgres:16-alpine
container_name: nc-db
restart: unless-stopped
env_file: .env
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
networks:
- nc_net
redis:
image: redis:7-alpine
container_name: nc-redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- nc_net
app:
image: nextcloud:29-apache
container_name: nc-app
restart: unless-stopped
depends_on:
- db
- redis
env_file: .env
environment:
POSTGRES_HOST: db
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS}
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://${NC_DOMAIN}
TRUSTED_PROXIES: 172.16.0.0/12
REDIS_HOST: redis
volumes:
- nc_data:/var/www/html
networks:
- nc_net
caddy:
image: caddy:2-alpine
container_name: nc-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- nc_net
volumes:
pg_data:
redis_data:
nc_data:
caddy_data:
caddy_config:
networks:
nc_net:
driver: bridge
EOF
Step 3: Configure the Caddy Reverse Proxy
Caddy handles HTTPS automatically via Let's Encrypt and forwards traffic to the Nextcloud app container.
cat > /opt/nextcloud/Caddyfile << 'EOF'
{
email {$CADDY_EMAIL}
}
https://{$NC_DOMAIN} {
reverse_proxy app:80
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy no-referrer
}
encode gzip
# Nextcloud-required: allow .well-known paths
@well-known path /.well-known/*
handle @well-known {
reverse_proxy app:80
}
}
EOF
Start the stack:
cd /opt/nextcloud
docker compose pull
docker compose up -d
# Watch first-boot initialization (60-120 seconds)
docker compose logs -f app
Wait for Apache/2.4.x ... configured -- resuming normal operations in the logs, then visit https://cloud.example.com.
Step 4: Post-Install Configuration
After first boot, configure Redis for caching and file locking, set trusted domains, and tune performance settings. Use the occ command-line tool inside the container rather than editing config.php directly.
Configure Redis and Trusted Proxy Settings
# Open a shell into the app container
docker compose exec app bash
# Configure Redis for local cache and file locking
php occ config:system:set memcache.local --value="\OC\Memcache\Redis"
php occ config:system:set memcache.locking --value="\OC\Memcache\Redis"
php occ config:system:set redis host --value=redis
php occ config:system:set redis port --value=6379 --type=integer
# Set trusted domains and proxy headers
php occ config:system:set trusted_domains 1 --value=cloud.example.com
php occ config:system:set overwriteprotocol --value=https
php occ config:system:set overwrite.cli.url --value=https://cloud.example.com
# Set maintenance window and phone region
php occ config:system:set maintenance_window_start --value=1
php occ config:system:set default_phone_region --value=US
# Exit the container
exit
Enable Background Jobs via Cron
Nextcloud requires regular background jobs for file scanning, notifications, and maintenance. Add a host cron job:
(crontab -l 2>/dev/null; echo "*/5 * * * * docker exec nc-app php -f /var/www/html/cron.php") | crontab -
# Verify the cron job is set
crontab -l
Step 5: Verification and Health Checks
Run through this checklist before declaring the deployment ready:
# 1. Check all containers are running
docker compose ps
# 2. Confirm HTTPS redirects from HTTP
curl -I http://cloud.example.com
# 3. Check TLS certificate is valid
curl -sv https://cloud.example.com/ 2>&1 | grep -E "SSL|subject|expire"
# 4. Run Nextcloud's built-in checks
docker compose exec app php occ status
docker compose exec app php occ check
# 5. Verify Redis is configured
docker compose exec app php occ config:system:get memcache.locking
# 6. Test WebDAV upload
curl -u admin:your-admin-password -T /etc/hostname \
https://cloud.example.com/remote.php/dav/files/admin/test.txt
Navigate to https://cloud.example.com/settings/admin/overview and resolve any warnings in the Security and setup warnings section. Common post-install fixes include enabling a memory cache, confirming background jobs mode is set to Cron, and setting the default phone region.
Tips, Gotchas, and Troubleshooting
Nextcloud Shows a Blank Page or 500 Error
The database initialization may still be running. Check docker compose logs app — if you see PDO connection failed, wait 30 seconds and refresh. If the error persists, verify the POSTGRES_* variables in .env match exactly between the db and app services. A trailing space in a password value is a common cause of authentication mismatches.
Trusted Domain Mismatch Warning
If Nextcloud shows "Access through untrusted domain", the NEXTCLOUD_TRUSTED_DOMAINS variable was not set before first boot. Fix it at runtime:
docker compose exec app php occ config:system:set trusted_domains 1 --value=cloud.example.com
Large File Uploads Fail
Caddy has no default upload limit, but the Nextcloud PHP container does. Increase it:
docker compose exec app bash -c "echo 'upload_max_filesize=10G' >> /usr/local/etc/php/conf.d/nextcloud.ini"
docker compose exec app bash -c "echo 'post_max_size=10G' >> /usr/local/etc/php/conf.d/nextcloud.ini"
docker compose restart app
Background Jobs Not Running
Confirm the cron job is active and the container name is correct:
# Check cron is scheduled
crontab -l
# Test the cron command manually
docker exec nc-app php -f /var/www/html/cron.php
# Set background mode to cron (not AJAX)
docker compose exec app php occ background:cron
Redis Connection Errors
Verify the Redis container is healthy and the Nextcloud app can reach it:
# Check Redis is running
docker compose ps redis
# Test connectivity from the app container
docker compose exec app redis-cli -h redis ping
# Re-run Redis configuration if needed
docker compose exec app php occ config:system:set memcache.local --value="\OC\Memcache\Redis"
docker compose exec app php occ config:system:set memcache.locking --value="\OC\Memcache\Redis"
docker compose exec app php occ config:system:set redis host --value=redis
Pro Tips
- Start with PostgreSQL — don't use SQLite for production. PostgreSQL handles concurrent writes better and is the recommended database for production Nextcloud.
- Enable Redis from day one — file locking without Redis causes "file currently locked" errors under concurrent use. Redis is not optional for multi-user deployments.
- Back up both database and data volume — use
pg_dumpfor PostgreSQL and archivenc_datafor files. Enable maintenance mode before backups. - Pin your image tags — use
nextcloud:29-apachenotnextcloud:latest. Upgrade one major version at a time with tested backups. - Monitor disk growth — user uploads, versions, and previews consume space fast. Set up alerts at 75% disk usage.
- Use object storage for scale — when local disk becomes limiting, configure S3-compatible primary storage in
config.phpto offload files.
Wrapping Up
A proper Nextcloud Docker setup gives you a self-hosted cloud storage platform that rivals commercial alternatives — without the per-seat fees, data residency concerns, or vendor lock-in. With Docker Compose, you get isolated services, persistent volumes, and straightforward upgrades. With PostgreSQL and Redis, you get a database that handles concurrent writes and a cache layer that keeps file locking reliable under load.
The setup is straightforward: create the project directory, write the Compose stack, configure Caddy for HTTPS, and run the post-install tuning commands. From there, add users, enable two-factor authentication, and configure external storage as your needs grow. The whole deployment takes under an hour and scales from a small team to hundreds of users.
Start with the Compose file in this guide, run through the verification checklist, and begin onboarding your team. Once you see files syncing across devices with zero third-party access, you'll understand why self-hosted cloud storage is the default for privacy-conscious organizations.
Need Help With Your Production Nextcloud Deployment?
If you're deploying Nextcloud for a larger team — with SSO integration, external storage, high-availability clustering, or compliance requirements — the sysbrix team can design and deploy it. We build self-hosted collaboration infrastructure that's production-ready, not just proof-of-concept.