Nextcloud Docker Installation: Ditch Google Drive for Your Own Cloud
Learn how to self-host your own Nextcloud instance on a VPS using Docker Compose. Complete step-by-step setup with database and reverse proxy.
System Requirements & Prerequisites
Before deploying Nextcloud, ensure your VPS meets the following hardware and software requirements:
- Virtual Private Server (VPS): A minimum of 2 GB RAM, 2 vCPUs, and sufficient storage (SSD/NVMe recommended).
- Operating System: Linux (Ubuntu 22.04 LTS or Debian 12 recommended).
- DNS: An
Arecord pointing your domain (e.g.,nextcloud.example.com) to the VPS public IP address. - Docker: Docker Engine version 24.0.0+ and Docker Compose v2.20.0+ installed.
- Ports: Ports
80and443must be open and not bound by any other web server (such as standalone Nginx or Apache).
Directory Structure
Create the required application directories. This structure separates config files, databases, and persistent data to simplify backups and maintenance:
mkdir -p /opt/nextcloud/data/{nextcloud,db,redis,traefik}
touch /opt/nextcloud/data/traefik/acme.json
chmod 600 /opt/nextcloud/data/traefik/acme.json
cd /opt/nextcloud
The resulting directory layout is structured as follows:
/opt/nextcloud/
├── docker-compose.yml
└── data/
├── nextcloud/ # Nextcloud application files & user data
├── db/ # PostgreSQL database cluster
├── redis/ # Redis cache data
└── traefik/
└── acme.json # Let's Encrypt certificates
Docker Compose Configuration
Create a docker-compose.yml file in /opt/nextcloud/ containing the configuration below. Replace nextcloud.example.com with your target domain, your-email@example.com with your Let's Encrypt registration email, and generate strong database passwords.
version: '3.8'
services:
db:
image: postgres:16-alpine
container_name: nextcloud-db
restart: always
volumes:
- ./data/db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=secure_postgres_db_password_here
healthcheck:
test: ["CMD-SHELL", "pg_isready -U nextcloud"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: always
volumes:
- ./data/redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
app:
image: nextcloud:28-apache
container_name: nextcloud-app
restart: always
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ./data/nextcloud:/var/www/html
environment:
- POSTGRES_HOST=db
- POSTGRES_DB=nextcloud
- POSTGRES_USER=nextcloud
- POSTGRES_PASSWORD=secure_postgres_db_password_here
- REDIS_HOST=redis
- NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.example.com
- TRUSTED_PROXIES=172.16.0.0/12 192.168.0.0/16 10.0.0.0/8 172.18.0.0/16
- OVERWRITEPROTOCOL=https
- OVERWRITECLIURL=https://nextcloud.example.com
- PHP_MEMORY_LIMIT=1024M
- PHP_UPLOAD_LIMIT=16G
- NC_default_phone_region=US
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.example.com`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
# CalDAV and CardDAV redirection middlewares
- "traefik.http.routers.nextcloud.middlewares=nextcloud-dav,nextcloud-headers"
- "traefik.http.middlewares.nextcloud-dav.redirectregex.regex=https://(.*)/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud-dav.redirectregex.replacement=https://$$1/remote.php/dav/"
- "traefik.http.middlewares.nextcloud-dav.redirectregex.permanent=true"
# Security Headers
- "traefik.http.middlewares.nextcloud-headers.headers.customresponseheaders.Strict-Transport-Security=max-age=15552000; includeSubDomains"
cron:
image: nextcloud:28-apache
container_name: nextcloud-cron
restart: always
entrypoint: /cron.sh
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- ./data/nextcloud:/var/www/html
traefik:
image: traefik:v2.10
container_name: traefik
restart: always
command:
- "--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=your-email@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./data/traefik:/letsencrypt"