Nextcloud Docker Setup: Own Your Cloud Storage and Stop Paying for Google Drive
Google Drive, Dropbox, OneDrive — convenient until you read the terms of service, hit a storage limit, or need your files to stay on infrastructure you control. Nextcloud is the open-source alternative that gives you file sync, sharing, calendar, contacts, document editing, and team collaboration on a server you own. This guide walks you through a complete Nextcloud Docker setup: from first container to a production-ready self-hosted cloud your team can actually use.
Prerequisites
- A Linux server (Ubuntu 22.04 LTS recommended) with at least 2GB RAM and 20GB+ disk (more for actual file storage)
- Docker Engine and Docker Compose v2 installed
- A domain name with DNS access — Nextcloud needs HTTPS for mobile sync and most features to work properly
- Ports 80 and 443 open on your firewall
- Basic familiarity with Docker Compose and Nginx
Check your starting point:
docker --version
docker compose version
free -h
df -h /
# Confirm ports are free
sudo ss -tlnp | grep -E ':80|:443'
What Nextcloud Actually Gives You
Nextcloud is more than file storage — it's a full collaboration platform. Understanding the scope helps you plan your setup correctly from the start.
Core Features
- File sync and share — desktop clients for Windows, macOS, and Linux; mobile apps for iOS and Android; WebDAV for anything else
- Calendar and contacts — CalDAV and CardDAV servers compatible with every major client
- Collaborative document editing — Nextcloud Office (powered by Collabora) for real-time document, spreadsheet, and presentation editing
- Talk — built-in video calls, chat, and screen sharing
- Photos — automatic photo backup from mobile, face recognition, timeline view
- External storage — mount S3, SFTP, Google Drive, or other sources as Nextcloud folders
- App ecosystem — 200+ apps for password management, project tracking, forms, and more
Why PostgreSQL Over SQLite
Nextcloud ships with SQLite support for testing, but it falls apart under real usage. For anything beyond a single personal user, use PostgreSQL from the start. Migrating databases later is painful — get this right on day one.
Deploying Nextcloud with Docker Compose
Project Structure
Set up a dedicated directory for the deployment:
mkdir -p ~/nextcloud
cd ~/nextcloud
The Docker Compose File
This Compose file runs Nextcloud with PostgreSQL for the database and Redis for caching and session locking — both required for any multi-user setup:
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: nextcloud_db
restart: unless-stopped
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- nextcloud_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U nextcloud"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: nextcloud_redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
networks:
- nextcloud_net
nextcloud:
image: nextcloud:28-apache
container_name: nextcloud
restart: unless-stopped
ports:
- "8080:80"
environment:
# Database
POSTGRES_HOST: postgres
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# Admin account (only used on first run)
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
# Trusted domain — required for security
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_DOMAIN}
# Redis
REDIS_HOST: redis
REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
# PHP settings
PHP_MEMORY_LIMIT: 1G
PHP_UPLOAD_LIMIT: 10G
volumes:
- nextcloud_data:/var/www/html
- ./data:/var/www/html/data # User file storage — point this at a large disk
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- nextcloud_net
volumes:
postgres_data:
redis_data:
nextcloud_data:
networks:
nextcloud_net:
Environment Variables
Create a .env file alongside your Compose file. Never hardcode credentials in the Compose file itself:
# .env
POSTGRES_PASSWORD=a-strong-postgres-password
REDIS_PASSWORD=a-strong-redis-password
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=a-strong-admin-password
NEXTCLOUD_DOMAIN=cloud.yourdomain.com
# Generate strong passwords:
# openssl rand -base64 24
Start the stack:
docker compose up -d
# First boot takes 2-3 minutes while Nextcloud initializes
# Watch until you see the Apache startup message
docker compose logs -f nextcloud
Once running, open http://localhost:8080 to confirm the UI loads. Don't do any configuration yet — set up HTTPS first.
Configuring HTTPS with Nginx
Nextcloud requires HTTPS for mobile clients, WebDAV sync, and several security features. Put Nginx in front as a TLS-terminating reverse proxy:
# /etc/nginx/sites-available/nextcloud
upstream nextcloud_backend {
server localhost:8080;
keepalive 32;
}
server {
listen 80;
server_name cloud.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name cloud.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/cloud.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cloud.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# Nextcloud-recommended headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
add_header X-XSS-Protection "1; mode=block" always;
# Large file uploads
client_max_body_size 10G;
client_body_timeout 300s;
# Proxy timeouts for large transfers
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_connect_timeout 300s;
location / {
proxy_pass http://nextcloud_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_request_buffering off;
}
# WebDAV performance
location = /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
}
Enable the config, get a cert, and reload:
sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/
sudo nginx -t
# Get Let's Encrypt certificate
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d cloud.yourdomain.com
# Reload with new config
sudo systemctl reload nginx
Now open https://cloud.yourdomain.com — you should see the Nextcloud login page over HTTPS with a valid certificate.
Post-Install Configuration
Running the Security and Setup Checklist
Nextcloud's admin panel has a built-in security scan. Log in as admin, go to Administration → Overview, and work through every warning. The most common ones on a fresh Docker install:
Fix the Background Jobs Scheduler
Nextcloud needs to run background jobs (cleanup, notifications, indexing). The default AJAX method is unreliable — switch to cron:
# Add to host crontab: crontab -e
# Run Nextcloud background jobs every 5 minutes
*/5 * * * * docker exec -u www-data nextcloud php -f /var/www/html/cron.php
# Then in Nextcloud admin UI:
# Administration → Basic settings → Background jobs → Select "Cron"
Configure the Nextcloud Config File
Several important settings live in config/config.php inside the container. Edit it directly for settings not exposed in the UI:
# Get a shell inside the container
docker exec -it nextcloud bash
# Edit config (nano or vi)
nano /var/www/html/config/config.php
Add or update these settings in the config array:
<?php
$CONFIG = array (
// Your domain — must match Nginx server_name
'trusted_domains' =>
array (
0 => 'cloud.yourdomain.com',
),
// Tell Nextcloud it's behind a reverse proxy
'overwrite.cli.url' => 'https://cloud.yourdomain.com',
'overwriteprotocol' => 'https',
'overwritehost' => 'cloud.yourdomain.com',
'trusted_proxies' =>
array (
0 => '172.0.0.0/8', // Docker network range
),
// Redis memory caching
'memcache.local' => '\\OC\\Memcache\\Redis',
'memcache.distributed' => '\\OC\\Memcache\\Redis',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'redis' =>
array (
'host' => 'redis',
'port' => 6379,
'password' => 'your-redis-password',
),
// Default phone region (for contacts/users)
'default_phone_region' => 'US',
// Increase max chunk size for faster uploads
'max_chunk_size' => 104857600, // 100MB
);
Installing the Nextcloud Desktop and Mobile Clients
Once HTTPS is working, connect the sync clients:
- Desktop (Windows/macOS/Linux): Download from nextcloud.com/install, enter your domain and credentials, choose which folders to sync
- iOS/Android: Search for Nextcloud in the App Store or Play Store — the official apps handle file sync, camera backup, and document editing
- WebDAV: Connect any WebDAV client to
https://cloud.yourdomain.com/remote.php/dav/files/USERNAME/
Tips, Gotchas, and Troubleshooting
"Untrusted Domain" Error After Accessing the UI
If Nextcloud shows an "Access through untrusted domain" error, your NEXTCLOUD_TRUSTED_DOMAINS env var or trusted_domains config array doesn't match the domain you're accessing. Fix it:
# Add the domain via occ CLI inside the container
docker exec -u www-data nextcloud php occ config:system:set \
trusted_domains 1 --value=cloud.yourdomain.com
# Verify the change
docker exec -u www-data nextcloud php occ config:system:get trusted_domains
File Uploads Failing for Large Files
Three limits need to align: PHP's upload limit, Nginx's client_max_body_size, and Nextcloud's own chunk size. The Compose file already sets PHP limits via environment variables. Confirm they're applied:
# Check active PHP limits inside the container
docker exec nextcloud php -i | grep -E 'upload_max|post_max|memory_limit'
# Should show:
# memory_limit => 1G
# post_max_size => 10G
# upload_max_filesize => 10G
Slow Performance — Redis Not Being Used
If Nextcloud feels sluggish, confirm Redis caching is active. Check the admin overview for "Memory caching" status, or verify via occ:
docker exec -u www-data nextcloud php occ config:system:get memcache.local
# Should return: \OC\Memcache\Redis
# Check Redis connection from inside Nextcloud container
docker exec nextcloud redis-cli -h redis -a your-redis-password ping
# Should return: PONG
Updating Nextcloud
Always back up before updating. Nextcloud updates can involve database migrations:
# 1. Back up the database first
docker exec nextcloud_db pg_dump -U nextcloud nextcloud \
> ~/backups/nextcloud-db-$(date +%Y-%m-%d).sql
# 2. Pull the new image (update version tag intentionally)
# Edit docker-compose.yml: nextcloud:28-apache → nextcloud:29-apache
docker compose pull
docker compose up -d
# 3. Run database migrations and repairs
docker exec -u www-data nextcloud php occ upgrade
docker exec -u www-data nextcloud php occ db:add-missing-indices
docker exec -u www-data nextcloud php occ maintenance:repair
# 4. Disable maintenance mode if it stuck on
docker exec -u www-data nextcloud php occ maintenance:mode --off
Scanning Files Added Outside the Web UI
If you add files directly to the data/ volume (bypassing Nextcloud), they won't appear in the UI until you trigger a rescan:
# Scan all files for a specific user
docker exec -u www-data nextcloud php occ files:scan username
# Scan all users
docker exec -u www-data nextcloud php occ files:scan --all
# Scan only a specific path
docker exec -u www-data nextcloud php occ files:scan --path="/username/files/Documents"
Pro Tips
- Store user data on a separate disk — the
./datavolume mount in the Compose file should point to your largest storage volume. Mount a dedicated data disk at/mnt/dataand update the path accordingly before your first run. - Enable server-side encryption selectively — Nextcloud's server-side encryption protects data at rest but adds CPU overhead and makes backups harder. Only enable it if your threat model requires it and you understand the key management implications.
- Use Preview Generator for thumbnails — install the Preview Generator app and run
occ preview:generate-allon a schedule so photo thumbnails are pre-generated rather than built on demand (which kills performance). - Pin image versions in production — use
nextcloud:28.0.4-apacheinstead ofnextcloud:28-apacheso updates are explicit and controlled. - Back up
config.phpseparately — it contains your encryption keys and instance secret. If you lose it, you can't decrypt your data even with a full database backup.
Wrapping Up
A complete Nextcloud Docker setup gives you a fully-featured cloud platform that replaces Google Drive, Google Photos, Google Contacts, Google Calendar, and Dropbox — running on a server you own, with no storage limits except your disk size and no monthly bill except your VPS cost.
Start with the Compose stack in this guide, get HTTPS working with Nginx, install the desktop and mobile clients, and run through the admin security checklist. Once that foundation is solid, explore the app ecosystem — Nextcloud Office, Talk, and Photos turn it from a file sync tool into a complete collaboration platform that gives you everything a SaaS tool would, on your own terms.
Need Nextcloud Deployed for a Team or Business?
Running Nextcloud for more than a handful of users means dealing with performance tuning, LDAP/SSO integration, backup automation, and storage scaling. The sysbrix team deploys and manages production Nextcloud instances for teams that need reliability and control — without the operational overhead of doing it yourself.
Talk to Us →