Deploying Nextcloud with Docker and External Storage
We earn commissions when you shop through the links on this page, at no additional cost to you.
Nextcloud is my go-to self-hosted file sync solution, but the real power kicks in when you connect external storage. In this guide, I'll walk you through deploying Nextcloud in Docker and wiring up NAS shares, local mounts, and even object storage. By the end, you'll have a bulletproof personal cloud that scales far beyond your container's local filesystem.
Why Docker for Nextcloud?
I've run Nextcloud on bare metal, in systemd services, and in containers. Docker wins every time because it isolates dependencies, makes upgrades trivial, and lets you spin up a test instance in seconds. When you need to test a major upgrade or plugin, you just clone your compose file, change the port, and run it on the side. Plus, Docker lets you version your entire setup in git—configuration as code.
The tricky part? External storage. Nextcloud itself runs in a container with its own filesystem, but your actual files probably live on a NAS, external drive, or object store. Getting that wiring right means properly mounting volumes and configuring Nextcloud's External Storage app. Get it wrong, and you'll have permission hell or your storage won't mount at all.
Infrastructure Setup: VPS or Homelab?
For a personal Nextcloud instance, I run it on a modest VPS. A $40/year server from RackNerd (around 1 vCore, 2GB RAM, 30GB SSD) handles 1-2 users comfortably. You can grab deals like that at racknerd.com/NewYear/ and use the storage for Nextcloud's MariaDB and app data, while mounting your files from an external NAS via NFS or SMB.
If you're in a homelab, run Nextcloud on your existing Docker host and mount your NAS storage directly. If you're remote-only, a cheap VPS with external storage (Backblaze B2, Wasabi, or Minio) is your best bet.
Docker Compose Setup
Here's a production-ready docker-compose.yml for Nextcloud with MariaDB, Redis for caching, and volume mounts for external storage:
version: '3.8'
services:
nextcloud-db:
image: mariadb:11
container_name: nextcloud-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- nextcloud-db:/var/lib/mysql
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "healthcheck.sh", "--su-mysql"]
interval: 10s
timeout: 5s
retries: 5
nextcloud-cache:
image: redis:7-alpine
container_name: nextcloud-cache
restart: always
command: redis-server --appendonly yes
volumes:
- nextcloud-cache:/data
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
nextcloud:
image: nextcloud:28
container_name: nextcloud
restart: always
depends_on:
nextcloud-db:
condition: service_healthy
nextcloud-cache:
condition: service_healthy
environment:
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_PASSWORD}
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_HOST: nextcloud-db
REDIS_HOST: nextcloud-cache
REDIS_PORT: 6379
TRUSTED_PROXIES: 127.0.0.1
OVERWRITE_PROTO: https
ports:
- "8080:80"
volumes:
- nextcloud-app:/var/www/html
- ./config:/var/www/html/config
- /mnt/nas-storage:/mnt/external-nas
- /mnt/local-media:/mnt/local-media
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/status.php"]
interval: 30s
timeout: 10s
retries: 3
volumes:
nextcloud-app:
nextcloud-db:
nextcloud-cache:
networks:
nextcloud-net:
driver: bridge
Before you start, create a .env file with strong passwords:
DB_ROOT_PASSWORD=your_very_strong_root_password_here
DB_PASSWORD=your_strong_nextcloud_db_password
NEXTCLOUD_PASSWORD=your_strong_admin_password
Then spin it up:
docker-compose up -d
Give it a minute for the database to initialize, then check status with docker-compose logs nextcloud.
.env to git. Add it to .gitignore immediately. If you're managing this in source control, use Docker secrets or a proper secrets manager like Vault or 1Password.Mounting External Storage in Docker
The volumes section of that compose file mounts two external paths: /mnt/nas-storage (your NAS share) and /mnt/local-media (a local drive or mount point). These paths must exist on your host before Nextcloud starts.
If you're mounting a NAS share via NFS, set it up first on your host:
sudo mkdir -p /mnt/nas-storage
sudo mount -t nfs 192.168.1.100:/share/nextcloud /mnt/nas-storage
Make sure the mount is readable by the www-data user inside the container (UID 33). Check your NAS permissions:
ls -la /mnt/nas-storage
If you see permission denied, you'll need to adjust NAS export settings or use uid=33,gid=33 in your NFS mount options.
For SMB/CIFS mounts (Windows shares), use:
sudo mount -t cifs //192.168.1.100/nextcloud /mnt/nas-storage \
-o username=nasuser,password=naspass,uid=33,gid=33
To make mounts persistent across reboots, add them to /etc/fstab.
Configuring External Storage in Nextcloud
Once Nextcloud is running, log in as admin and navigate to Settings → Administration → External Storage. You'll need to enable the External Storage app first.
For the paths you mounted in Docker, create a new external storage entry:
- Folder name:
NAS Storage - Storage type:
Local - Configuration:
/mnt/external-nas(the path inside the container) - Available for: Check the users or groups who should access it
If you're using S3-compatible storage (Wasabi, Backblaze B2, or Minio), choose Amazon S3 and enter:
- Bucket:
my-nextcloud-bucket - Hostname:
s3.wasabisys.com(for Wasabi) or your Minio endpoint - Access key & secret: Your S3 credentials
- Region: Your bucket region
Click Check to verify the connection. If it fails, check Docker logs for permission issues:
docker-compose logs nextcloud | grep -i storage
Performance Tuning
Redis caching is crucial when you're pulling files from a slow NAS. I also recommend enabling the Preview Generator app to pre-generate thumbnails instead of generating them on-the-fly:
docker-compose exec nextcloud php occ app:enable preview
docker-compose exec nextcloud php occ preview:generate-all
Add a cron job to regenerate previews nightly:
docker-compose exec nextcloud php occ preview:generate-all -vv
Also enable trusted_domains in config/config.php if you access Nextcloud from multiple domains:
'trusted_domains' =>
array (
0 => 'localhost',
1 => 'nextcloud.example.com',
2 => '192.168.1.50',
),
OVERWRITE_PROTO: https in your compose file and let Caddy handle the HTTPS.Backup Strategy
Your Nextcloud data lives in three places: the container volume, the external storage mount, and the database. Back them all up:
#!/bin/bash
# Backup script for Nextcloud
BACKUP_DIR="/backups/nextcloud"
mkdir -p $BACKUP_DIR
# Database backup
docker-compose exec -T nextcloud-db mysqldump \
-u nextcloud -p${DB_PASSWORD} nextcloud > \
$BACKUP_DIR/nextcloud-db-$(date +%Y%m%d).sql
# Config and app data
tar -czf $BACKUP_DIR/nextcloud-app-$(date +%Y%m%d).tar.gz \
./config nextcloud-app:/var/www/html
# External storage (rsync to avoid copying everything)
rsync -av /mnt/nas-storage/ /backups/nextcloud-storage/
echo "Backup complete"
Schedule this with cron:
0 2 * * * /path/to/backup-nextcloud.sh
Troubleshooting Common Issues
Permission denied on external storage: Check that the mount is readable by UID 33 (www-data). If using NFS, add no_root_squash to your NAS export and mount with uid=33,gid=33.
Slow file sync: Enable Redis (already in the compose file) and increase the number of background workers. Edit config.php and set 'backgroundjobs_mode' => 'cron', then add a Docker exec cron task.
Database won't start: Check that no other MariaDB instance is running on port 3306. If upgrading from a much older Nextcloud version, the database schema might need migration—the container logs will tell you.
Can't reach Nextcloud from outside: Make sure your reverse proxy is set up correctly and trusted_domains includes your external domain. Don't forget to set OVERWRITE_PROTO: https if using HTTPS.
Going Further
Once you have this running, consider adding:
- Automated backups with Watchtower and a backup container
- OnlyOffice or Collabora CODE for collaborative document editing
- Full-text search with Elasticsearch
- LDAP/Active Directory integration for corporate-style user management
The external storage setup I've shown scales to petabytes—you're not limited by Nextcloud's container size, only by your network and storage backend.
Discussion