Skip to Content

Nextcloud Docker Setup: Deploy a Self-Hosted Cloud Storage Platform in Under an Hour

Build a production-ready Nextcloud instance with Docker Compose, PostgreSQL, Redis, and HTTPS — your files, your rules, no vendor lock-in.

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:

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_dump for PostgreSQL and archive nc_data for files. Enable maintenance mode before backups.
  • Pin your image tags — use nextcloud:29-apache not nextcloud: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.php to 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.

Talk to Us →

Open WebUI Setup Guide: Build a Self-Hosted ChatGPT Alternative in Minutes
Deploy Open WebUI with Docker Compose to run local LLMs behind a beautiful ChatGPT-style interface — private, offline, and fully under your control.