9.6 KiB
Nextcloud Stack - Automated Deployment with Ansible
Complete automation for deploying a self-hosted Nextcloud productivity stack on Ubuntu-based LXC/VPS servers.
Features
- 12 Services: Nextcloud, OnlyOffice, Excalidraw, Obsidian, Homarr, Dockhand, Uptime Kuma, PostgreSQL, Redis, Caddy, Watchtower
- Automatic SSL: Let's Encrypt certificates via Caddy
- Secure Management: Tailscale-only access for admin interfaces
- Auto-updates: Watchtower with safety exclusions for critical services
- Automated Backups: Daily database backups, weekly volume backups (30-day retention)
- Monitoring: Uptime Kuma with configurable alerts
- Multi-server: Deploy to multiple VPS servers with unique domains
Quick Start
Prerequisites
-
Control Machine (your laptop):
- Ansible 2.14+ installed
- SSH access to target servers
-
Target Server(s):
- Ubuntu 20.04+ (LXC container or VPS)
- Root or sudo access
- Minimum 100GB disk space
- Ports 80, 443 available
-
DNS Configuration (BEFORE deployment):
cloud.yourdomain.com → YOUR_SERVER_IP office.yourdomain.com → YOUR_SERVER_IP draw.yourdomain.com → YOUR_SERVER_IP notes.yourdomain.com → YOUR_SERVER_IP home.yourdomain.com → YOUR_SERVER_IP manage.yourdomain.com → YOUR_SERVER_IP uptime.yourdomain.com → YOUR_SERVER_IP
Installation
-
Clone this repository:
git clone <your-repo-url> cd ansible-nextcloud-deployment -
Run interactive setup:
./setup.shThis will ask for:
- Server IP addresses and domains
- Your name and email
- Admin credentials
- Subdomain preferences
- Tailscale auth key (optional)
- Monitoring email
- Ansible Vault password
-
Deploy the stack:
make deployOr manually:
ansible-playbook playbooks/site.yml --ask-vault-pass -
Access your services:
- Nextcloud:
https://cloud.yourdomain.com - OnlyOffice:
https://office.yourdomain.com - Homarr (Tailscale only):
https://home.yourdomain.com - Uptime Kuma (Tailscale only):
https://uptime.yourdomain.com
- Nextcloud:
Project Structure
ansible-nextcloud-deployment/
├── setup.sh # Interactive configuration script
├── Makefile # Convenient command shortcuts
├── ansible.cfg # Ansible configuration
├── inventory/
│ ├── hosts.yml # Generated by setup.sh
│ └── group_vars/all/
│ ├── vars.yml # Public variables
│ └── vault.yml # Encrypted secrets
├── playbooks/
│ ├── site.yml # Main orchestrator
│ ├── 01-preflight-checks.yml # Pre-deployment validation
│ ├── 02-system-setup.yml # System packages & security
│ ├── 03-docker-setup.yml # Docker installation
│ ├── 04-tailscale-setup.yml # VPN setup
│ ├── 05-deploy-stack.yml # Docker Compose deployment
│ ├── 06-configure-caddy.yml # Reverse proxy configuration
│ ├── 07-setup-backups.yml # Backup automation
│ └── 08-post-deployment.yml # Final verification
└── roles/
├── nextcloud_stack/
│ └── templates/
│ ├── docker-compose.yml.j2
│ └── env.j2
└── caddy/
└── templates/
└── Caddyfile.j2
Makefile Commands
make help # Show all commands
make setup # Run interactive setup
make ping # Test connectivity
make check # Run preflight checks
make deploy # Full deployment
make deploy-dry # Dry run (no changes)
make status # Show container status
make logs # View container logs
make backup # Run manual backup
make restart # Restart all containers
make edit-vault # Edit encrypted secrets
Service Stack
| Service | Purpose | Port | Access | Auto-Update |
|---|---|---|---|---|
| Caddy | Reverse proxy + SSL | 80, 443 | Public | ✅ |
| Nextcloud | File sync & collaboration | Internal | Public (via Caddy) | ❌ Manual |
| PostgreSQL 18 | Database | Internal | Internal only | ❌ Manual |
| Redis 7 | Cache layer | Internal | Internal only | ❌ Manual |
| OnlyOffice | Document editor | Internal | Public (via Caddy) | 👁️ Monitor |
| Excalidraw | Whiteboard tool | Internal | Public (via Caddy) | ✅ |
| Obsidian | Note-taking | Internal | Public (via Caddy) | ✅ |
| Homarr | Dashboard | Internal | Tailscale only | ✅ |
| Dockhand | Container manager | 3003 | Tailscale only | ✅ |
| Uptime Kuma | Monitoring | Internal | Tailscale only | ✅ |
| Watchtower | Auto-updater | N/A | N/A | N/A |
Security Features
- Firewall: UFW configured with minimal open ports
- Fail2ban: SSH brute-force protection
- Automatic Updates: Unattended security updates enabled
- Secret Management: Ansible Vault encryption for all credentials
- Access Control: Management UIs restricted to Tailscale network
- SSL/TLS: Automatic Let's Encrypt certificates
- Container Isolation: Dedicated Docker network
Backup System
- Daily: PostgreSQL database dumps (3:00 AM)
- Weekly: Full volume backups (Sundays)
- Retention: 30 days
- Location:
/opt/nextcloud-stack/backups/ - rclone: Pre-installed for future remote backups
Manual Backup
ssh user@server
cd /opt/nextcloud-stack
./backup.sh
Restore from Backup
See BACKUP_RESTORE.md for detailed procedures.
Tailscale Setup
If you didn't provide an auth key during setup:
ssh user@server
sudo tailscale up
Then access management interfaces via Tailscale IP or MagicDNS.
Updating Services
Safe to Auto-Update (Watchtower handles)
- Caddy, Excalidraw, Obsidian, Homarr, Dockhand, Uptime Kuma
Monitor Only (notifications, no auto-update)
- OnlyOffice
Manual Update Required
- Nextcloud
- PostgreSQL
- Redis
Updating Nextcloud Manually
ssh user@server
cd /opt/nextcloud-stack
# 1. Backup
./backup.sh
# 2. Enable maintenance mode
docker exec -u www-data next php occ maintenance:mode --on
# 3. Update
docker compose pull next
docker compose up -d next
# 4. Run database migrations
docker exec -u www-data next php occ upgrade
# 5. Disable maintenance mode
docker exec -u www-data next php occ maintenance:mode --off
Troubleshooting
DNS Not Configured
Error: Let's Encrypt fails to issue certificates
Solution: Ensure all DNS A records point to your server IP. Check with:
dig +short cloud.yourdomain.com
LXC Container Issues
Error: Docker fails to start
Solution: On LXC host, enable nested virtualization:
lxc config set CONTAINER_NAME security.nesting true
lxc restart CONTAINER_NAME
Port Already in Use
Error: Port 80 or 443 already bound
Solution: Check what's using the ports:
sudo ss -tlnp | grep ':80\|:443'
sudo systemctl stop apache2 # or nginx
Nextcloud Stuck in Maintenance Mode
docker exec -u www-data next php occ maintenance:mode --off
View Logs
cd /opt/nextcloud-stack
docker compose logs -f [service-name]
Post-Deployment Tasks
-
Login to Nextcloud:
- URL:
https://cloud.yourdomain.com - Username: (set during setup)
- Password: (stored in vault)
- URL:
-
Setup Uptime Kuma:
- URL:
https://uptime.yourdomain.com(via Tailscale) - Create admin account on first visit
- Configure monitors for your services
- URL:
-
Configure Homarr Dashboard:
- URL:
https://home.yourdomain.com(via Tailscale) - Add service tiles
- Customize layout
- URL:
-
Configure OnlyOffice in Nextcloud:
- Nextcloud Settings → Administration → Office
- Document Editing Service:
https://office.yourdomain.com
-
Test Backups:
make backup
File Locations on Server
/opt/nextcloud-stack/
├── docker-compose.yml
├── .env
├── backup.sh
├── configs/
│ ├── caddy/Caddyfile
│ └── nextcloud/
├── data/
│ ├── homarr/
│ └── obsidian/
└── backups/
├── database/
└── volumes/
Environment Variables
All secrets are stored in inventory/group_vars/all/vault.yml (encrypted).
To edit:
make edit-vault
Multiple Server Deployment
The setup script supports multiple servers. Each can have its own domain:
Server 1: 192.168.1.100 → cloud1.example.com
Server 2: 192.168.1.101 → cloud2.anotherdomain.net
Server 3: 192.168.1.102 → personal.mydomain.org
Deploy to all:
make deploy
Deploy to specific server:
ansible-playbook playbooks/site.yml --limit server_hostname --ask-vault-pass
Maintenance
Check Container Status
make status
Restart Services
make restart
Update Images (safe services only)
make update
View Logs
make logs
Uninstallation
ssh user@server
cd /opt/nextcloud-stack
docker compose down -v # WARNING: Deletes all data!
cd /opt
rm -rf nextcloud-stack
Support
- See TROUBLESHOOTING.md for common issues
- See BACKUP_RESTORE.md for backup procedures
- See AGENTS.md for development guidelines
License
MIT
Acknowledgments
- Nextcloud team
- Caddy web server
- Ansible community
- All open-source contributors
Built with ❤️ for self-hosting enthusiasts