Self-Host Nextcloud on Docker 2026
Self-Host Nextcloud on Docker 2026
TL;DR
Nextcloud is the most complete self-hosted cloud storage platform available in 2026. A single Docker Compose stack — Nextcloud, PostgreSQL, Redis, and Traefik — runs comfortably on a $6/month VPS with 1GB RAM and replaces Google Drive, Google Calendar, Google Contacts, and basic document collaboration for up to 5–10 users. You own the data, control the encryption, and pay $72/year instead of $120+/year for Google One. This guide covers both Nextcloud AIO (All-in-One) and the manual postgres+redis stack, SSL termination with Traefik, and a production-grade backup strategy using rclone.
Key Takeaways
- Minimum specs: 1GB RAM, 1 vCPU, 20GB storage — a $6/month Hetzner or DigitalOcean VPS handles 5 users comfortably
- Two deployment paths: Nextcloud AIO (single container, automated SSL, batteries-included) vs manual stack (more control, easier to debug, better for existing infrastructure)
- SSL automation: Traefik with Let's Encrypt handles cert provisioning and renewal with zero manual certificate management
- Data replacement: Files, Calendar (CalDAV), Contacts (CardDAV), Notes, Bookmarks, and Talk (video chat) — Google Workspace feature parity for personal and small team use
- Backup minimum: Nextcloud requires three-component backups — database (PostgreSQL dump), file data (volume snapshot), and Nextcloud config — skipping any one leaves you unable to restore
- Offsite backup: rclone + a $1–2/month object storage bucket (Backblaze B2, Wasabi) makes backups genuinely disaster-proof
Why Self-Host Nextcloud in 2026
Google Drive price increases, Google One storage bundling, and heightened awareness of cloud data retention policies have pushed more teams and individuals toward self-hosted alternatives. Nextcloud's case has also strengthened: version 29 (released 2024) improved performance significantly, the Nextcloud Office integration (Collabora Online) matured, and the ecosystem of apps — including AI assistant integrations — expanded.
The practical argument for self-hosting in 2026:
Cost: Google One's 2TB plan costs $99.99/year. A Hetzner CX22 (2 vCPU, 4GB RAM, 40GB SSD) costs €5.77/month — about $75/year — with the option to attach cheap additional volumes for $0.052/GB/month. At 2TB of actual storage on Hetzner volumes, total cost is roughly $125/year, comparable to Google One but with no per-user pricing and full data sovereignty.
Data ownership: Nextcloud data stays on your server, in your jurisdiction, encrypted at rest if you configure it. Google scans files for policy compliance and may suspend accounts for content violations. There is no equivalent risk with a self-hosted instance.
Feature scope: Nextcloud Hub 9 covers Files (with desktop/mobile sync clients), Calendar (CalDAV), Contacts (CardDAV), Mail client, Notes, Talk (video and audio calls), Forms, Deck (Kanban), and Nextcloud Office (Collabora Online for document editing). For small teams, this eliminates multiple SaaS subscriptions.
Compliance: For GDPR, HIPAA, or SOC 2-adjacent requirements where data must stay within specific geographic boundaries, self-hosted Nextcloud is a straightforward answer. Cloud storage providers require complex DPAs and rely on mechanisms that regulatory bodies periodically challenge.
Choosing a Deployment Path: AIO vs Manual Stack
Nextcloud offers two distinct Docker deployment strategies with meaningfully different tradeoffs.
Nextcloud AIO (All-in-One)
Nextcloud AIO is a master container that manages the entire stack: Nextcloud itself, PostgreSQL, Redis, Nextcloud Office (Collabora), Clamav (virus scanning), Imaginary (media processing), Elasticsearch (optional full-text search), and backup. It exposes an admin interface on port 8080 for configuration.
AIO Pros:
- Single-command deployment
- Automated SSL via Caddy (built-in)
- Automated daily backups via built-in backup container
- All components versioned and tested together
- Nextcloud-recommended path for most users
AIO Cons:
- Less transparent — harder to debug individual components
- Requires port 443 to be available on the host (conflicts with existing Nginx/Traefik setups)
- Less flexible for custom reverse proxy configurations
- AIO's Caddy handles SSL, which may conflict with existing certificate management
AIO Quick Start:
docker run \
--sig-proxy=false \
--name nextcloud-aio-mastercontainer \
--restart always \
--publish 80:80 \
--publish 8080:8080 \
--publish 8443:8443 \
--volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \
--volume /var/run/docker.sock:/var/run/docker.sock:ro \
nextcloud/all-in-one:latest
Access https://your-server-ip:8080 to complete setup through the web UI. AIO handles the rest.
Manual Stack (PostgreSQL + Redis + Nginx/Traefik)
The manual stack gives you full control over each component and integrates cleanly with existing infrastructure. This is the right choice if you already run Traefik as a reverse proxy for other services, need custom Nginx configuration, or want to understand exactly what's running.
This guide uses the manual stack for the full setup walkthrough.
Manual Stack: Docker Compose Setup
Prerequisites
- A VPS with 1GB+ RAM and Ubuntu 22.04 or 24.04
- Docker and Docker Compose v2 installed
- A domain pointing to your server's IP (e.g.,
cloud.yourdomain.com) - Ports 80 and 443 open in your firewall
Directory Structure
mkdir -p /opt/nextcloud/{data,config}
cd /opt/nextcloud
docker-compose.yml
services:
traefik:
image: traefik:v3.0
restart: always
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-letsencrypt:/letsencrypt
networks:
- proxy
db:
image: postgres:16-alpine
restart: always
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
networks:
- internal
redis:
image: redis:7-alpine
restart: always
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
networks:
- internal
nextcloud:
image: nextcloud:29-apache
restart: always
depends_on:
- db
- redis
environment:
POSTGRES_HOST: db
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.yourdomain.com
REDIS_HOST: redis
REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
APACHE_DISABLE_REWRITE_IP: 1
TRUSTED_PROXIES: traefik
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.yourdomain.com
volumes:
- nextcloud-data:/var/www/html
- ./config:/var/www/html/config
networks:
- proxy
- internal
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`cloud.yourdomain.com`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Frame-Options=SAMEORIGIN"
- "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Content-Type-Options=nosniff"
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.regex=^https://(.*)/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.replacement=https://$${1}/remote.php/dav/"
- "traefik.http.middlewares.nextcloud-caldav.redirectregex.permanent=true"
- "traefik.http.routers.nextcloud.middlewares=nextcloud-headers,nextcloud-caldav"
nextcloud-cron:
image: nextcloud:29-apache
restart: always
depends_on:
- db
- redis
volumes:
- nextcloud-data:/var/www/html
- ./config:/var/www/html/config
entrypoint: /cron.sh
networks:
- internal
volumes:
db-data:
redis-data:
nextcloud-data:
traefik-letsencrypt:
networks:
proxy:
external: false
internal:
external: false
Environment File
# /opt/nextcloud/.env
DB_PASSWORD=change_this_strong_password_32chars
REDIS_PASSWORD=change_this_redis_password_16chars
ADMIN_PASSWORD=change_this_admin_password
Generate strong passwords:
openssl rand -base64 32 | tr -d '/+=' | head -c 32
Start the Stack
cd /opt/nextcloud
docker compose up -d
# Watch Nextcloud initialize (takes 30–60 seconds on first boot)
docker compose logs -f nextcloud
Once initialization completes, access https://cloud.yourdomain.com. Traefik handles Let's Encrypt certificate provisioning automatically on first request.
SSL with Traefik: How It Works
Traefik's ACME integration handles certificate provisioning through the ACME TLS challenge:
- Traefik receives the first HTTPS request for
cloud.yourdomain.com - It contacts Let's Encrypt and proves domain control via a TLS-ALPN-01 challenge (no HTTP port 80 required)
- Let's Encrypt issues a 90-day certificate
- Traefik stores it in
/letsencrypt/acme.jsonand automatically renews at 30 days before expiry
The acme.json file is stored in the traefik-letsencrypt Docker volume — back this up alongside your other volumes.
Caddy as an Alternative
Caddy is simpler to configure for single-service setups:
# Caddyfile
cloud.yourdomain.com {
reverse_proxy nextcloud:80
header {
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
}
redir /.well-known/carddav /remote.php/dav/ permanent
redir /.well-known/caldav /remote.php/dav/ permanent
}
Caddy's advantage is zero-configuration HTTPS — it reads the Caddyfile and handles all certificate management. Traefik's advantage is centralized routing for multi-service environments, which is preferable if you're running multiple Docker services behind one reverse proxy.
Post-Installation Configuration
Optimize Nextcloud for Performance
Edit /opt/nextcloud/config/config.php after the first run:
<?php
$CONFIG = array(
// Memory caching with APCu (local) + Redis (distributed)
'memcache.local' => '\\OC\\Memcache\\APCu',
'memcache.distributed' => '\\OC\\Memcache\\Redis',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'redis' => array(
'host' => 'redis',
'port' => 6379,
'password' => 'your_redis_password',
'timeout' => 1.5,
),
// Set default phone region
'default_phone_region' => 'US',
// Maintenance window (off-peak updates)
'maintenance_window_start' => 1,
// Chunked upload size (4MB for slower connections)
'upload_max_filesize' => '16G',
'max_upload_size' => '16G',
);
Increase PHP Upload Limits
The default Nextcloud Apache image has conservative upload limits. Override via environment or a custom .htaccess:
# Set via occ command inside the container
docker compose exec -u www-data nextcloud php occ config:system:set upload_max_filesize --value="16G"
docker compose exec -u www-data nextcloud php occ config:system:set post_max_size --value="16G"
Run Maintenance Tasks
# Check for missing database indices
docker compose exec -u www-data nextcloud php occ db:add-missing-indices
# Run mimetype migrations
docker compose exec -u www-data nextcloud php occ maintenance:mimetype:update-js
docker compose exec -u www-data nextcloud php occ maintenance:mimetype:update-db
# Repair inconsistencies
docker compose exec -u www-data nextcloud php occ maintenance:repair
Backup Strategy
A complete Nextcloud backup requires three components: the PostgreSQL database, the Nextcloud file data volume, and the configuration files. Restoring from only two of three will result in a broken installation.
Component 1: PostgreSQL Database Dump
#!/bin/bash
# backup-db.sh
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/var/backups/nextcloud"
mkdir -p "$BACKUP_DIR"
docker compose -f /opt/nextcloud/docker-compose.yml exec -T db \
pg_dump -U nextcloud nextcloud \
| gzip > "$BACKUP_DIR/nextcloud-db-$DATE.sql.gz"
echo "Database backed up to $BACKUP_DIR/nextcloud-db-$DATE.sql.gz"
Component 2: File Data Volume
For the data volume, enable Nextcloud maintenance mode before snapshotting to ensure consistency:
#!/bin/bash
# backup-data.sh
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/var/backups/nextcloud"
mkdir -p "$BACKUP_DIR"
# Enable maintenance mode
docker compose -f /opt/nextcloud/docker-compose.yml exec -u www-data nextcloud \
php occ maintenance:mode --on
# Backup the data volume (using a temporary Alpine container)
docker run --rm \
-v nextcloud_nextcloud-data:/source:ro \
-v "$BACKUP_DIR":/backup \
alpine \
tar czf "/backup/nextcloud-data-$DATE.tar.gz" -C /source .
# Disable maintenance mode
docker compose -f /opt/nextcloud/docker-compose.yml exec -u www-data nextcloud \
php occ maintenance:mode --off
echo "Data backed up to $BACKUP_DIR/nextcloud-data-$DATE.tar.gz"
Component 3: Configuration Backup
#!/bin/bash
# backup-config.sh
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/var/backups/nextcloud"
tar czf "$BACKUP_DIR/nextcloud-config-$DATE.tar.gz" \
/opt/nextcloud/config/ \
/opt/nextcloud/.env \
/opt/nextcloud/docker-compose.yml
echo "Config backed up to $BACKUP_DIR/nextcloud-config-$DATE.tar.gz"
Offsite Backup with rclone
Local backups protect against file corruption and accidental deletion but not server failure. rclone pushes backups to any S3-compatible object storage:
Install and configure rclone:
curl https://rclone.org/install.sh | sudo bash
rclone config
# Follow prompts to add a Backblaze B2 or Wasabi remote named "backup"
Upload backup to object storage:
#!/bin/bash
# backup-offsite.sh
BACKUP_DIR="/var/backups/nextcloud"
REMOTE="backup:your-bucket-name/nextcloud"
# Upload all backup files from today
rclone copy "$BACKUP_DIR" "$REMOTE" \
--include "*.{gz,tar.gz}" \
--min-age 0 \
--transfers 4 \
--progress
# Remove local backups older than 7 days
find "$BACKUP_DIR" -name "*.gz" -mtime +7 -delete
echo "Offsite backup complete"
Full backup cron (runs at 2 AM daily):
# /etc/cron.d/nextcloud-backup
0 2 * * * root /opt/nextcloud/scripts/backup-db.sh >> /var/log/nextcloud-backup.log 2>&1
10 2 * * * root /opt/nextcloud/scripts/backup-data.sh >> /var/log/nextcloud-backup.log 2>&1
20 2 * * * root /opt/nextcloud/scripts/backup-config.sh >> /var/log/nextcloud-backup.log 2>&1
30 2 * * * root /opt/nextcloud/scripts/backup-offsite.sh >> /var/log/nextcloud-backup.log 2>&1
Backblaze B2 costs: $0.006/GB/month for storage, $0.01/GB for downloads. A 500GB Nextcloud backup costs ~$3/month to store offsite.
Updating Nextcloud
Nextcloud releases minor updates frequently. The Docker image approach makes updates straightforward but requires care with database migrations:
cd /opt/nextcloud
# Pull updated images
docker compose pull
# Enable maintenance mode before updating
docker compose exec -u www-data nextcloud php occ maintenance:mode --on
# Stop and restart with new images
docker compose down
docker compose up -d
# Run database upgrades (Nextcloud handles this automatically on startup)
# Wait for container to complete initialization, then verify
docker compose logs -f nextcloud
# Disable maintenance mode
docker compose exec -u www-data nextcloud php occ maintenance:mode --off
# Run post-upgrade cleanup
docker compose exec -u www-data nextcloud php occ db:add-missing-indices
docker compose exec -u www-data nextcloud php occ upgrade
Never skip major versions. If you're on Nextcloud 27, update to 28 before updating to 29. Nextcloud blocks version skips.
Resource Usage and Scaling
| Users | Storage | Recommended VPS | Monthly Cost |
|---|---|---|---|
| 1–5 | Up to 500GB | 1 vCPU, 1GB RAM + volume | $6–10 |
| 5–15 | 500GB–2TB | 2 vCPU, 2GB RAM + volume | $10–20 |
| 15–50 | 2TB+ | 4 vCPU, 4GB RAM + volume | $25–50 |
| 50+ | Multi-TB | 8 vCPU, 8GB RAM + dedicated storage | $80+ |
Redis significantly reduces database load for multi-user setups. Without Redis, concurrent file sync clients cause PostgreSQL lock contention. With Redis as a file locking backend, 10–15 concurrent users on a 2GB VPS perform well.
For large files (video, archives), configure chunked uploads and ensure Apache/PHP is configured for large upload sizes.
Desktop and Mobile Sync
Nextcloud's official sync clients are the equivalent of the Google Drive desktop app:
- Desktop: Nextcloud client for Windows, macOS, Linux — selective sync, virtual file system on Windows, bandwidth throttling
- Mobile: Nextcloud iOS and Android apps — camera auto-upload, offline access, Talk integration
Configure the desktop client to point to https://cloud.yourdomain.com and authenticate with your Nextcloud account. The sync client handles delta sync — only changed portions of modified files are transferred.
Decision Framework
Self-host Nextcloud if:
- You need more than 2TB of storage and want to avoid per-user SaaS pricing
- Data must stay within a specific jurisdiction (GDPR, HIPAA, sovereignty requirements)
- You're replacing Google Drive + Calendar + Contacts for a small team
- You already run Docker infrastructure and adding a service is routine
- Budget matters: $6–15/month beats comparable SaaS at scale
Stay on Google Drive/One if:
- You're a solo user with under 200GB of storage needs (Google One 200GB is $2.99/month)
- You rely on Google Workspace integrations (Docs in-browser collaborative editing, Gmail, Meet)
- Server administration is not something you want to manage
- You need mobile-first workflows with deep OS integration on Android
Consider Nextcloud AIO instead of this manual setup if:
- You're new to Docker and want the simplest possible setup
- You don't already run a reverse proxy for other services
- You want Nextcloud Office (Collabora) without configuring it manually
Related Guides
Self-hosting Nextcloud often goes alongside other infrastructure decisions. Related reads on OSSAlt:
- Gitea vs GitHub: Self-Hosted Git 2026 — add a self-hosted Git forge to your stack alongside Nextcloud
- Self-Host Plausible vs Google Analytics 2026 — replace Google Analytics with privacy-first, self-hosted web analytics
- Self-Host Mattermost: Slack Alternative for Teams 2026 — add team messaging to your self-hosted stack with Mattermost
Related: Nextcloud vs Google Workspace Migration 2026 · How to Migrate from Dropbox to Nextcloud 2026 · Self-Hosting Guide: Nextcloud 2026