Self-hosting a personal cloud like Nextcloud gives you unparalleled control over your data. But setting it up to be reliable, secure, and maintainable can be a challenge. That’s where Docker Compose shines!
The docker-compose.yaml file you’re looking at is an excellent example of a modern, production-ready Nextcloud stack. It goes far beyond the basics, incorporating key services for database, caching, scheduled tasks, automatic updates, and robust security.
Let’s break down this powerful configuration piece by piece.
The Core Trio: Nextcloud, Database, and Cache
Any powerful web application needs a solid foundation, and this setup provides exactly that with three essential services:
1. db: The Data Workhorse (MariaDB)
Nextcloud needs a database to store all its file metadata, user information, and configurations. This setup chooses MariaDB:11, a community-developed fork of MySQL known for its performance and reliability.
- Custom Commands: The
commandsection sets specific SQL isolation and logging parameters (--transaction-isolation=READ-COMMITTED,--binlog-format=ROW), which are best practices for high-integrity applications like Nextcloud. - Security: All sensitive credentials (
MYSQL_ROOT_PASSWORD,MYSQL_PASSWORD) are loaded securely from external environment variables (prefixed with${...}). - Persistence: A named volume (
db:/var/lib/mysql) ensures your precious data persists even if the container is rebuilt. - Health Check: A
healthcheckguarantees the application container only starts when the database is fully ready to accept connections.
2. redis: The Speed Booster (Redis)
Nextcloud heavily benefits from an in-memory object cache to speed up common operations. The Redis:alpine container serves this purpose, acting as a lightning-fast intermediary between the Nextcloud application and the database.
3. nextcloud: The Application (Nextcloud:apache)
This is the heart of the stack—your personal cloud application.
- Dependencies: The
depends_onblock is crucial here. It waits for bothdbandredisto pass theirservice_healthychecks before starting, preventing frustrating startup errors. - Networking: The
portsentry exposes the Nextcloud service (usually on port 80 in the container) to a custom port on your host system, like8080, using${NEXTCLOUD_HTTP_PORT:-8080}:80. - Storage: It uses two types of volumes:
- A named volume (
nextcloud:/var/www/html) for the application files and configuration. - A bind mount (
./data:/var/www/html/data) to link the actual user files to a directory on your host machine (./data), making backups and external management easier.
- A named volume (
- Object Storage: The configuration includes numerous Amazon S3-style environment variables (
OBJECTSTORE_S3_...). This indicates the setup is ready to offload its primary file storage to an object storage service, like AWS S3 or MinIO, for massive scalability.
Automation and Maintenance Services
This configuration includes two services that make maintaining the stack a breeze.
4. cron: Scheduled Tasks
Nextcloud needs background jobs (like checking for updates, moving trash, and processing notifications) to run regularly. This separate cron service runs the same Nextcloud image but with a different entrypoint (/cron.sh) to execute these tasks on a schedule without interfering with the main web application. It also waits for db and redis to be healthy.
5. watchtower: Automatic Updates
The Watchtower container is a fantastic maintenance tool. It monitors your running containers and automatically pulls the latest images and restarts the containers when updates are available. This ensures your stack stays current with minimal manual intervention. It’s configured to run on a CRON schedule (e.g., daily at midnight) and includes WATCHTOWER_CLEANUP: "true" to remove old, unused images.
Security First: The CrowdSec Integration 🛡️
What truly sets this setup apart is the inclusion of CrowdSec, a modern, collaborative intrusion prevention system. This adds a crucial layer of security to your Nextcloud instance.
6. crowdsec: The Brain
This service acts as the security agent.
- Logs Analysis: It’s configured to monitor various logs by mounting
/var/logfrom the host. Critically, theCOLLECTIONSenvironment variable tells it to actively look for threats in Linux, Apache2, and Nextcloud logs. - Labeling: Notice the
nextcloudservice has a label:- "crowdsec.enable=true". This is how CrowdSec knows to inspect that container’s logs for bad behavior. - Banning: With
cap_add: NET_ADMIN, CrowdSec has the power to ban malicious IPs at the network firewall level.
7. crowdsec-bouncer: The Enforcer
The bouncer is the intermediary that takes the banning decisions from the crowdsec agent and applies them to your services. In this configuration, it’s set up to work as a generic bouncer, likely intended to be integrated with an external reverse proxy (like Traefik, hence the image name) to block traffic before it even hits Nextcloud. It relies on a secure API key (CROWDSEC_BOUNCER_API_KEY) to communicate with the CrowdSec agent.
Conclusion: A Nextcloud Powerhouse
This docker-compose.yaml file isn’t just a basic Nextcloud install; it’s a self-healing, highly performant, and automatically secured private cloud solution. By leveraging separate containers for the database, cache, cron jobs, and a complete security suite, you get maximum stability and security for your self-hosted data.
Ready to deploy this powerhouse? You’ll just need to create a .env file to define all those necessary environment variables, especially the passwords! Happy self-hosting! 🚀
services:
db:
image: mariadb:11
container_name: nextcloud-db
restart: unless-stopped
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW --log-bin=binlog
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
volumes:
- db:/var/lib/mysql
networks:
- nextcloud_net
healthcheck:
test: ["CMD", "mariadb", "-h", "localhost", "-u", "nextcloud", "-p${MYSQL_PASSWORD}", "-e", "SELECT 1"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
container_name: nextcloud-redis
restart: unless-stopped
volumes:
- redis_data:/data
networks:
- nextcloud_net
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
nextcloud:
image: nextcloud:apache
container_name: nextcloud-app
restart: unless-stopped
ports:
- "${NEXTCLOUD_HTTP_PORT:-8080}:80"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
# Database
MYSQL_HOST: db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
# Redis (local)
REDIS_HOST: redis
# S3 / object store (primary or external)
OBJECTSTORE_S3_BUCKET: ${OBJECTSTORE_S3_BUCKET:-}
OBJECTSTORE_S3_REGION: ${OBJECTSTORE_S3_REGION:-us-east-1}
OBJECTSTORE_S3_HOST: ${OBJECTSTORE_S3_HOST:-s3.amazonaws.com}
OBJECTSTORE_S3_PORT: ${OBJECTSTORE_S3_PORT:-443}
OBJECTSTORE_S3_KEY: ${OBJECTSTORE_S3_KEY:-}
OBJECTSTORE_S3_SECRET: ${OBJECTSTORE_S3_SECRET:-}
OBJECTSTORE_S3_SSL: ${OBJECTSTORE_S3_SSL:-false}
OBJECTSTORE_S3_USEPATH_STYLE: ${OBJECTSTORE_S3_USEPATH:-true}
OBJECTSTORE_S3_LEGACYAUTH: false
# Nextcloud admin & proxy
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER:-admin}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD:-}
NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS:-localhost}
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-}
OVERWRITEPROTOCOL: ${OVERWRITEPROTOCOL:-https}
volumes:
# PHP files + config
- nextcloud:/var/www/html
# optional bind mount for persistent raw data folder beside the volume
- ./data:/var/www/html/data
networks:
- nextcloud_net
labels:
- "crowdsec.enable=true"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
interval: 30s
timeout: 10s
retries: 3
cron:
image: nextcloud:apache
container_name: nextcloud-cron
restart: unless-stopped
entrypoint: /cron.sh
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
MYSQL_HOST: db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
REDIS_HOST: redis
# mirror relevant S3 envs for object storage if used by cron jobs
OBJECTSTORE_S3_BUCKET: ${OBJECTSTORE_S3_BUCKET:-}
OBJECTSTORE_S3_REGION: ${OBJECTSTORE_S3_REGION:-}
OBJECTSTORE_S3_HOST: ${OBJECTSTORE_S3_HOST:-}
OBJECTSTORE_S3_PORT: ${OBJECTSTORE_S3_PORT:-}
OBJECTSTORE_S3_KEY: ${OBJECTSTORE_S3_KEY:-}
OBJECTSTORE_S3_SECRET: ${OBJECTSTORE_S3_SECRET:-}
OBJECTSTORE_S3_AUTOCREATE: ${OBJECTSTORE_S3_AUTOCREATE:-false}
volumes:
- nextcloud:/var/www/html
- ./data:/var/www/html/data
networks:
- nextcloud_net
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
WATCHTOWER_CLEANUP: "true"
# Use either schedule CRON or poll interval. CRON example below (daily at midnight):
WATCHTOWER_SCHEDULE: "${WATCHTOWER_SCHEDULE:-0 0 * * *}"
WATCHTOWER_INCLUDE_STOPPED: "${WATCHTOWER_INCLUDE_STOPPED:-false}"
WATCHTOWER_REVIVE_STOPPED: "${WATCHTOWER_REVIVE_STOPPED:-false}"
WATCHTOWER_NOTIFICATION_URL: ${WATCHTOWER_NOTIFICATION_URL:-}
networks:
- nextcloud_net
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: crowdsec
restart: unless-stopped
cap_add:
- NET_ADMIN
environment:
COLLECTIONS: ${CROWDSEC_COLLECTIONS:-crowdsecurity/linux crowdsecurity/apache2 crowdsecurity/nextcloud}
GID: ${CROWDSEC_GID:-1000}
volumes:
- crowdsec_data:/var/lib/crowdsec/data
- ./crowdsec/config:/etc/crowdsec:rw
- /var/log:/var/log:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- "${CROWDSEC_LAPI_PORT:-8081}:8080"
networks:
- nextcloud_net
depends_on:
- nextcloud
crowdsec-bouncer:
image: fbonalair/traefik-crowdsec-bouncer:latest
container_name: crowdsec-bouncer
restart: unless-stopped
environment:
CROWDSEC_BOUNCER_API_KEY: ${CROWDSEC_BOUNCER_API_KEY:-}
CROWDSEC_AGENT_HOST: crowdsec:8080
depends_on:
- crowdsec
networks:
- nextcloud_net
networks:
nextcloud_net:
driver: bridge
volumes:
db:
nextcloud:
redis_data:
crowdsec_data: