Self-hosted • Privacy-first • No tracking
Home / Homelab / Nextcloud 34 Docker Deployment: The Complete Homelab Guide (2026)
Homelab #self-hosted#homelab#docker-compose#nextcloud#postgresql ⏱ 15 min • 👁 5 • Sep 02, 2026

Nextcloud 34 Docker Deployment: The Complete Homelab Guide (2026)

Step-by-step Nextcloud 34.0.3 Docker Compose deployment guide for homelabs: requirements, full configs, hardening, and troubleshooting.

AdSense — Top (970x90) • Responsive
Nextcloud 34 Docker Deployment: The Complete Homelab Guide (2026)

Introduction

Self-hosting Nextcloud remains the gold standard for reclaiming control over your files, calendars, contacts, and collaborative documents. As of August 2026, Nextcloud Hub 10 (version 34.0.3) is the latest official release, bringing significant performance improvements, an overhauled text editor, and better federation support. Running it via Docker Compose is the most maintainable approach for a homelab—it isolates dependencies, simplifies upgrades, and makes rollbacks trivial.

This guide walks you through a production-grade deployment of Nextcloud 34.0.3 using Docker Compose. You will learn how to structure persistent volumes, configure the required database and cache services, set up a reverse proxy with automatic SSL certificates, and implement backup strategies that actually work. We will also cover common pitfalls that break fresh installations—such as permission mismatches and proxy header misconfigurations—with concrete solutions.

By the end of this article, you will have a fully functional Nextcloud instance accessible via your own domain with HTTPS, automatic certificate renewal, and a solid foundation for adding Collabora or OnlyOffice later. Every command and configuration file is provided in full, ready to be copied and adapted to your environment.

This guide assumes you have basic familiarity with the Linux command line, Docker, and environment variables. All instructions are written for a Debian/Ubuntu-based host, but the concepts translate directly to other distributions.

Prerequisites / Requirements

Before starting, ensure your hardware and software meet the minimum expectations for a smooth Nextcloud experience. The following table summarizes typical requirements—actual usage varies based on the number of users, file sizes, and enabled apps.

Component Minimum Requirement Recommended for Typical Homelab Notes
CPU 2 cores 4+ cores Encryption, video previews, and database queries are CPU-bound. More cores help with concurrent background jobs.
RAM 4 GB 8 GB RAM usage is typically 1-2 GB for the database and PHP-FPM plus additional memory for Redis cache. Estimated total consumption of 2-4 GB is common with default settings.
Storage 50 GB free 1 TB+ (or NAS mount) Use a dedicated disk or partition for the nextcloud-data volume. Avoid NFS for the database volume—latency causes corruption.
OS Linux (Debian/Ubuntu recommended) LTS version Docker Engine 24+ and Docker Compose v2 are required.
Software Docker Engine, Docker Compose v2 Latest stable Install via official Docker repositories. Verify with docker --version and docker compose version.
Domain Valid domain name Subdomain like cloud.example.com Required for trusted domain configuration and Let's Encrypt SSL. A dynamic DNS service works if your IP changes.
Ports 80 and 443 open 80 and 443 open Needed for ACME HTTP-01 challenge. If you use DNS-01 challenge, only 443 is required.

Software versions verified in this guide:

  • Nextcloud: v34.0.3 (published 2026-08-13)
  • PostgreSQL: 16-alpine (LTS, supported by Nextcloud)
  • Redis: 7.4-alpine (latest stable)
  • Caddy: 2.8-alpine (reverse proxy with automatic HTTPS)

Step-by-Step Installation

Step 1: Prepare the Directory Structure and Environment File

Create the base directory and navigate into it. Then create a .env file to store all secrets. This file must never be committed to version control.

mkdir -p ~/nextcloud && cd ~/nextcloud && \
touch .env && \
chmod 600 .env

Populate the .env file with the following content. Replace the placeholder values with strong, unique passwords. Generate them with openssl rand -base64 32 if you need inspiration.

# Database credentials
POSTGRES_DB=nextcloud
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD=change_this_db_password_strong

# Nextcloud admin account (first-run setup)
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=change_this_admin_password_strong

# Domain for reverse proxy
DOMAIN=cloud.example.com

# Timezone and locale
TZ=UTC

# Nextcloud version - check official releases before pinning
NEXTCLOUD_VERSION=34.0.3

Security warning: The .env file contains plaintext secrets. Keep it outside any Git repository or add .env to your .gitignore file. Back it up separately using a password manager.

Step 2: Create the Docker Compose File

Create a docker-compose.yml file in the same directory. This configuration defines four services: the database, the cache, the reverse proxy, and Nextcloud itself. All secrets are referenced from the .env file.

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7.4-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: nextcloud:${NEXTCLOUD_VERSION:-latest}
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "8080:80"
    volumes:
      - nextcloud-data:/var/www/html
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - REDIS_HOST=redis
      - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
      - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
      - NEXTCLOUD_TRUSTED_DOMAINS=${DOMAIN}
      - PHP_UPLOAD_LIMIT=10G
      - PHP_MEMORY_LIMIT=1G
      - TZ=${TZ}

  proxy:
    image: caddy:2.8-alpine
    restart: unless-stopped
    depends_on:
      - app
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - caddy-data:/data
      - caddy-config:/config
    environment:
      - DOMAIN=${DOMAIN}
    command: caddy reverse-proxy --from ${DOMAIN} --to app:80

volumes:
  db-data:
  redis-data:
  nextcloud-data:
  caddy-data:
  caddy-config:

Important notes:

  • The Nextcloud image version is controlled by the NEXTCLOUD_VERSION variable. If you omit it from .env, the tag latest is used. Check the official Nextcloud Docker GitHub releases before pinning a specific version—the one above may be outdated by the time you read this.
  • We expose Nextcloud on port 8080 temporarily. The Caddy reverse proxy handles external traffic on ports 80 and 443. This prevents port conflicts on your host.
  • The PHP_UPLOAD_LIMIT and PHP_MEMORY_LIMIT are set to generous values. Adjust them based on your expected file sizes and available RAM.

Step 3: Launch the Stack

Start all services in detached mode. This pulls the images and creates the containers.

cd ~/nextcloud && \
docker compose up -d

Verify that all containers are running and healthy:

docker compose ps

Wait for the database to be ready—the app service will retry the connection automatically. Check the logs of the app service to monitor progress:

docker compose logs -f app

Step 4: Complete the Web Installation

Open your browser and navigate to http://localhost:8080. You will see the Nextcloud setup page. Since you provided NEXTCLOUD_ADMIN_USER and NEXTCLOUD_ADMIN_PASSWORD environment variables, the admin account is already created—you will be logged in directly. The installer will also detect the PostgreSQL and Redis settings from the environment variables.

If you did not set the admin credentials in .env, you will see the setup wizard. In that case, enter your desired admin username and password, leave the database fields as they are (they are pre-filled from environment variables), and click Install.

Step 5: Configure Trusted Domains

Nextcloud rejects requests with an untrusted Host header. Since we are accessing via localhost:8080, you must add it to the trusted domains list. Edit the config/config.php file inside the nextcloud-data volume.

First, find the container's config path:

docker compose exec app sh -c "cat /var/www/html/config/config.php"

Look for the 'trusted_domains' array. It should already contain your domain from ${DOMAIN}. Add localhost:8080 to the array:

docker compose exec app sh -c "sed -i \"/'trusted_domains' =>/a\    1 => 'localhost:8080',\" /var/www/html/config/config.php"

Alternatively, edit the file directly with vi or nano inside the container:

docker compose exec app vi /var/www/html/config/config.php

Add the line 1 => 'localhost:8080', within the array. Save and exit. The change takes effect immediately—no restart required.

Step 6: Set Up the Reverse Proxy with SSL

Our Caddy container automatically obtains and renews Let's Encrypt certificates for your domain. It proxies all traffic to the app service on port 80. This eliminates the need for manual SSL configuration inside Nextcloud.

To test the reverse proxy, ensure your domain resolves to your server's public IP. Then access https://${DOMAIN}. Caddy will automatically redirect HTTP to HTTPS and handle certificate issuance. If the certificate issuance fails, check your DNS records and that ports 80 and 443 are open in your firewall.

Step 7: Configure Nextcloud for the Reverse Proxy

Nextcloud needs to know it is behind a trusted proxy to generate correct URLs and handle redirects. Since Caddy sets the X-Forwarded-* headers, you must add a trusted_proxies entry in config.php.

Edit the config file again:

docker compose exec app vi /var/www/html/config/config.php

Add the following lines before the closing );:

  'trusted_proxies' =>
  array (
    0 => '192.168.100.0/24',
    1 => 'proxy',
  ),
  'overwrite.cli.url' => 'https://' . getenv('DOMAIN'),
  'overwritehost' => getenv('DOMAIN'),
  'overwriteprotocol' => 'https',

The proxy hostname refers to the Caddy container on the same Docker network. The IP range is a fallback for direct connections from your LAN. Adjust the subnet to match your local network.

Step 8: Apply Recommended Nextcloud System Config

Nextcloud has several configuration options that improve security and performance. Add these to your config.php:

  'default_phone_region' => 'US',
  'remember_login_cookie_lifetime' => 60 * 60 * 24 * 15,
  'session_lifetime' => 60 * 60 * 24,
  'skeletondirectory' => '',
  'enable_preview' => true,
  'preview_max_x' => 2048,
  'preview_max_y' => 2048,
  'jpeg_quality' => 80,
  'maintenance_window_start' => 2,

These settings disable the default skeleton files, increase preview resolution, and set a maintenance window for background jobs. Adjust the region code and cookie lifetimes to your preference.

Step 9: Enable Background Jobs via Cron

The default AJAX-based background job processing is unreliable. Switch to Cron. Edit the config.php and add:

  'backgroundjobs_mode' => 'cron',

Then add a cron job on your host that triggers the Nextcloud cron script every 5 minutes:

crontab -e

Add the following line, adjusting the path to your nextcloud directory:

*/5 * * * * cd /home/youruser/nextcloud && docker compose exec -T app php cron.php

Step 10: Verify the Installation

Run the built-in verification from the command line:

docker compose exec -T app php occ status && \
docker compose exec -T app php occ check

The occ status command shows the version and installation path. The check command validates system configuration and reports any issues. Address any warnings—especially those related to database indices or missing PHP modules.

Advanced Setup / Optimization

Using an External Reverse Proxy (Nginx/Traefik)

If you already run a reverse proxy like Nginx or Traefik, you can remove the proxy service from the Compose file. Instead, configure your existing proxy to forward requests to localhost:8080. Ensure you pass the X-Forwarded-* headers and set trusted_proxies accordingly in config.php. For Nginx, the relevant location block is:

location / {
    proxy_pass http://127.0.0.1:8080;
    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 X-Forwarded-Host $host;
}

Backup Strategy

A robust backup covers the database, the Nextcloud data volume, and the config files. The following script creates consistent backups using pg_dump for the database and tar for the data volume. Run it daily via a cron job.

#!/bin/bash
BACKUP_DIR=~/nextcloud-backups
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR

# Backup database
docker compose exec -T db pg_dump -U ${POSTGRES_USER} ${POSTGRES_DB} | gzip > $BACKUP_DIR/db_$TIMESTAMP.sql.gz

# Backup Nextcloud data and config
docker run --rm --volumes-from $(docker compose ps -q app) -v $BACKUP_DIR:/backup alpine tar czf /backup/nextcloud_data_$TIMESTAMP.tar.gz /var/www/html

# Remove backups older than 14 days
find $BACKUP_DIR -name "*.gz" -mtime +14 -delete

Store the backups on a different physical drive or a remote NAS. Test the restoration process at least once after initial setup.

Performance Tuning

The default PHP memory limit of 1 GB is sufficient for most operations. If you run heavy collaborative editing or machine learning apps, consider increasing it. The database connection pool is handled by PostgreSQL automatically—no tuning needed for typical homelab loads.

For large file uploads, the PHP_UPLOAD_LIMIT environment variable controls the maximum size. Set it to match your expected largest file. Note that your reverse proxy must also allow large bodies—Caddy does by default, but Nginx requires client_max_body_size.

Optional Hardening

The following security measures are optional and may break functionality if applied blindly. Test them in a staging environment first.

  1. Run containers as non-root: Add user: "33:33" (www-data) to the app service. This requires ensuring the nextcloud-data volume is owned by UID 33. Run id -u and id -g on your host to check your own UID/GID, then adjust permissions accordingly.

  2. Drop Linux capabilities: Add the following to the app service:

    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETUID
      - SETGID
      - DAC_OVERRIDE

This restricts the container's privileges. Nextcloud needs CHOWN and DAC_OVERRIDE to manage file permissions inside the volume. Test carefully—some apps (e.g., Collabora) require additional capabilities.

  1. Enable read-only root filesystem: Add read_only: true to the app service. You must mount temporary directories for PHP sessions and uploads:
    tmpfs:
      - /tmp
      - /var/www/html/sessions

This prevents any writes to the container's filesystem, forcing all changes to persist in the volume. Many third-party apps fail under this setting.

Troubleshooting

Common Error Cause Solution
502 Bad Gateway from Caddy The app container is not ready or crashed. Check logs with docker compose logs app. Common cause: database connection failure. Ensure the db healthcheck passes.
Trusted domain error after setup The domain you used to access Nextcloud is not in trusted_domains. Add the exact hostname (including port if non-standard) to config.php under trusted_domains.
Database connection failed during install Wrong credentials in .env or database container not healthy. Verify the .env file values match the POSTGRES_* variables. Run docker compose exec db pg_isready -U nextcloud to test.
413 Request Entity Too Large on upload The reverse proxy or PHP upload limit is too low. Increase PHP_UPLOAD_LIMIT in docker-compose.yml and restart. For Nginx, set client_max_body_size to match.
Permission denied when writing to data directory Volume ownership mismatch between host and container. Check the UID of the www-data user in the container (docker compose exec app id www-data). Then chown -R 33:33 on the host volume directory. Run id -u to see your host UID—it may differ.
504 Gateway Timeout on large operations PHP-FPM or database query timeout. Increase PHP_MEMORY_LIMIT and set pm.max_children in the PHP-FPM pool. Consider adding more RAM or optimizing database indexes with occ db:add-missing-indices.
SSL certificate not issued by Caddy DNS not propagated or ports 80/443 blocked. Verify DNS with dig ${DOMAIN}. Ensure your firewall allows inbound traffic on both ports. Check Caddy logs with docker compose logs proxy.

Conclusion

Deploying Nextcloud 34.0.3 with Docker Compose is a straightforward process when you understand the interplay between the application, database, and reverse proxy. This guide provided a complete, production-ready stack with persistent storage, automatic SSL, and a solid backup strategy. The key to long-term success is maintaining your .env file securely, keeping your images updated, and regularly testing your backups.

Nextcloud is highly extensible—once your base installation is stable, explore the app store for collaborative editing, video conferencing, or AI-powered features. Remember to check the official Nextcloud documentation before enabling experimental apps. Your instance is now ready to serve as the central hub for your personal cloud.

FAQ

1. How do I update Nextcloud to a newer version?

Update the NEXTCLOUD_VERSION variable in your .env file to the new version tag, then run docker compose pull app && docker compose up -d. The container will start with the new image and run automatic migration scripts. Always back up the database and data volume before upgrading. Check the official changelog for any manual steps.

2. Can I use SQLite instead of PostgreSQL for a small setup?

Yes, but it is not recommended. Nextcloud's own documentation states that SQLite is only suitable for testing. PostgreSQL or MySQL offer better concurrency and reliability. The Compose file in this guide uses PostgreSQL—switching to SQLite would require removing the db service and changing environment variables, which is more work than just running PostgreSQL.

3. How do I move my Nextcloud data volume to a different location on the host?

Stop the stack with docker compose down. Copy the volume contents to the new location using docker run --rm -v nextcloud_nextcloud-data:/source -v /new/path:/target alpine cp -a /source/. /target/. Then edit docker-compose.yml to add a bind mount instead of a named volume for nextcloud-data, pointing to /new/path. Finally, run docker compose up -d.

4. What is the default admin password if I didn't set it in .env?

If you do not provide NEXTCLOUD_ADMIN_USER and NEXTCLOUD_ADMIN_PASSWORD, the web installer will prompt you to create an admin account. The password you enter is stored in the database. If you lose it, you can reset it from the command line: docker compose exec -T app php occ user:resetpassword admin.

5. Is it safe to expose Nextcloud directly to the internet without a reverse proxy?

Technically yes, but you lose automatic HTTPS and gain unnecessary complexity. Nextcloud expects to be behind a trusted proxy that handles SSL termination. Running it directly on port 443 requires manual certificate management and exposes the PHP server to more attack vectors. Using Caddy or another reverse proxy is the community-recommended approach.

AdSense — In-article (responsive)

Related Guides