Implement persistent block storage for Nextcloud user data, separating application and data layers: OpenTofu Changes: - tofu/volumes.tf: Create and attach Hetzner Volumes per client - Configurable size per client (default 100 GB for dev) - ext4 formatted, attached but not auto-mounted - tofu/variables.tf: Add nextcloud_volume_size to client config - tofu/terraform.tfvars: Set volume size for dev client (100 GB ~€5.40/mo) Ansible Changes: - ansible/roles/nextcloud/tasks/mount-volume.yml: New mount tasks - Detect volume device automatically - Format if needed, mount at /mnt/nextcloud-data - Add to fstab for persistence - Set correct permissions for www-data - ansible/roles/nextcloud/tasks/main.yml: Include volume mounting - ansible/roles/nextcloud/templates/docker-compose.nextcloud.yml.j2: - Use host mount /mnt/nextcloud-data/data instead of Docker volume - Keep app code in Docker volume (nextcloud-app) - User data now on Hetzner Volume Scripts: - scripts/resize-client-volume.sh: Online volume resizing - Resize via Hetzner API - Expand filesystem automatically - Show cost impact - Verify new size Documentation: - docs/storage-architecture.md: Complete storage guide - Architecture diagrams - Volume specifications - Sizing guidelines - Operations procedures - Performance considerations - Troubleshooting guide - docs/volume-migration.md: Step-by-step migration - Safe migration from Docker volumes - Rollback procedures - Verification checklist - Timeline estimates Benefits: ✅ Data independent from server instance ✅ Resize storage without rebuilding server ✅ Easy data migration between servers ✅ Better separation of concerns (app vs data) ✅ Simplified backup strategy ✅ Cost-optimized (pay for what you use) Volume Pricing: - 50 GB: ~€2.70/month - 100 GB: ~€5.40/month - 250 GB: ~€13.50/month - Resizable online, no downtime Note: Existing clients require manual migration Follow docs/volume-migration.md for safe migration procedure Closes #18 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
398 lines
9.3 KiB
Markdown
398 lines
9.3 KiB
Markdown
# Volume Migration Guide
|
|
|
|
Step-by-step guide for migrating existing Nextcloud clients from local Docker volumes to Hetzner Volumes.
|
|
|
|
## Overview
|
|
|
|
This guide covers migrating an existing client (like `dev`) that currently stores Nextcloud data in a Docker volume to the new Hetzner Volume architecture.
|
|
|
|
**Migration is SAFE and REVERSIBLE** - we keep the old data until verification is complete.
|
|
|
|
## Prerequisites
|
|
|
|
- Client currently deployed and running
|
|
- SSH access to the server
|
|
- Hetzner API token (`HCLOUD_TOKEN`)
|
|
- SOPS age key for secrets (`SOPS_AGE_KEY_FILE`)
|
|
- At least 30 minutes of maintenance window
|
|
|
|
## Migration Steps
|
|
|
|
### Phase 1: Preparation
|
|
|
|
#### 1. Verify Current State
|
|
|
|
```bash
|
|
# Check client is running
|
|
./scripts/client-status.sh dev
|
|
|
|
# Check current data location
|
|
cd ansible
|
|
ansible dev -i hcloud.yml -m shell -a "docker inspect nextcloud | jq '.[0].Mounts'"
|
|
```
|
|
|
|
Expected output shows Docker volume:
|
|
```json
|
|
{
|
|
"Type": "volume",
|
|
"Name": "nextcloud-data",
|
|
"Source": "/var/lib/docker/volumes/nextcloud-data/_data",
|
|
"Destination": "/var/www/html"
|
|
}
|
|
```
|
|
|
|
#### 2. Check Data Size
|
|
|
|
```bash
|
|
# Check how much data we're migrating
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
du -sh /var/lib/docker/volumes/nextcloud-data/_data/data"
|
|
```
|
|
|
|
Note the size - you'll need a volume at least this big (we recommend 2x for growth).
|
|
|
|
#### 3. Notify Users
|
|
|
|
⚠️ **Important**: Inform users that Nextcloud will be unavailable during migration (typically 10-30 minutes depending on data size).
|
|
|
|
### Phase 2: Create and Attach Volume
|
|
|
|
#### 4. Update OpenTofu Configuration
|
|
|
|
Already done if you're following the issue #18 implementation:
|
|
|
|
```hcl
|
|
# tofu/terraform.tfvars
|
|
clients = {
|
|
dev = {
|
|
# ... existing config ...
|
|
nextcloud_volume_size = 100 # Adjust based on current data size
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 5. Apply OpenTofu Changes
|
|
|
|
```bash
|
|
cd tofu
|
|
|
|
# Review changes
|
|
tofu plan
|
|
|
|
# Apply - this creates the volume and attaches it
|
|
tofu apply
|
|
```
|
|
|
|
Expected output:
|
|
```
|
|
+ hcloud_volume.nextcloud_data["dev"]
|
|
+ hcloud_volume_attachment.nextcloud_data["dev"]
|
|
```
|
|
|
|
The volume is now attached to the server but not yet mounted.
|
|
|
|
### Phase 3: Stop Services and Mount Volume
|
|
|
|
#### 6. Enable Maintenance Mode
|
|
|
|
```bash
|
|
cd ansible
|
|
|
|
# Enable Nextcloud maintenance mode
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
docker exec -u www-data nextcloud php occ maintenance:mode --on"
|
|
```
|
|
|
|
#### 7. Stop Nextcloud Containers
|
|
|
|
```bash
|
|
# Stop Nextcloud and cron (keep database and redis running)
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
docker stop nextcloud nextcloud-cron"
|
|
```
|
|
|
|
#### 8. Mount the Volume
|
|
|
|
```bash
|
|
# Run Ansible volume mounting tasks
|
|
ansible-playbook -i hcloud.yml playbooks/deploy.yml \
|
|
--limit dev \
|
|
--tags volume
|
|
```
|
|
|
|
This will:
|
|
- Find the volume device
|
|
- Format as ext4 (if needed)
|
|
- Mount at `/mnt/nextcloud-data`
|
|
- Create data directory with correct permissions
|
|
- Add to `/etc/fstab` for persistence
|
|
|
|
#### 9. Verify Mount
|
|
|
|
```bash
|
|
# Check mount is successful
|
|
ansible dev -i hcloud.yml -m shell -a "df -h /mnt/nextcloud-data"
|
|
ansible dev -i hcloud.yml -m shell -a "ls -la /mnt/nextcloud-data"
|
|
```
|
|
|
|
### Phase 4: Migrate Data
|
|
|
|
#### 10. Copy Data to Volume
|
|
|
|
```bash
|
|
# Copy all data from Docker volume to Hetzner Volume
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
rsync -avh --progress \
|
|
/var/lib/docker/volumes/nextcloud-data/_data/data/ \
|
|
/mnt/nextcloud-data/data/" -b
|
|
```
|
|
|
|
This will take some time depending on data size. Progress is shown.
|
|
|
|
**Estimated times:**
|
|
- 1 GB: ~30 seconds
|
|
- 10 GB: ~5 minutes
|
|
- 50 GB: ~20 minutes
|
|
- 100 GB: ~40 minutes
|
|
|
|
#### 11. Verify Data Copy
|
|
|
|
```bash
|
|
# Check data was copied
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
du -sh /mnt/nextcloud-data/data"
|
|
|
|
# Verify file count matches
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
find /var/lib/docker/volumes/nextcloud-data/_data/data -type f | wc -l && \
|
|
find /mnt/nextcloud-data/data -type f | wc -l"
|
|
```
|
|
|
|
Both counts should match.
|
|
|
|
#### 12. Fix Permissions
|
|
|
|
```bash
|
|
# Ensure correct ownership
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
chown -R www-data:www-data /mnt/nextcloud-data/data" -b
|
|
```
|
|
|
|
### Phase 5: Update Configuration and Restart
|
|
|
|
#### 13. Update Docker Compose
|
|
|
|
Already done if you're following the issue #18 implementation. The new template uses:
|
|
|
|
```yaml
|
|
volumes:
|
|
- /mnt/nextcloud-data/data:/var/www/html/data
|
|
```
|
|
|
|
#### 14. Deploy Updated Configuration
|
|
|
|
```bash
|
|
# Deploy updated docker-compose.yml
|
|
ansible-playbook -i hcloud.yml playbooks/deploy.yml \
|
|
--limit dev \
|
|
--tags nextcloud,docker
|
|
```
|
|
|
|
This will:
|
|
- Update docker-compose.yml
|
|
- Restart Nextcloud with new volume mounts
|
|
|
|
#### 15. Disable Maintenance Mode
|
|
|
|
```bash
|
|
# Turn off maintenance mode
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
docker exec -u www-data nextcloud php occ maintenance:mode --off"
|
|
```
|
|
|
|
### Phase 6: Verification
|
|
|
|
#### 16. Test Nextcloud Access
|
|
|
|
```bash
|
|
# Check containers are running
|
|
ansible dev -i hcloud.yml -m shell -a "docker ps | grep nextcloud"
|
|
|
|
# Test HTTPS endpoint
|
|
curl -I https://nextcloud.dev.vrije.cloud
|
|
```
|
|
|
|
Expected: HTTP 200 OK
|
|
|
|
#### 17. Login and Verify Files
|
|
|
|
1. Open https://nextcloud.dev.vrije.cloud in browser
|
|
2. Login with admin credentials
|
|
3. Navigate to Files
|
|
4. Check that all files are visible
|
|
5. Try uploading a new file
|
|
6. Try downloading an existing file
|
|
|
|
#### 18. Run Files Scan
|
|
|
|
```bash
|
|
# Scan all files to update Nextcloud's database
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
docker exec -u www-data nextcloud php occ files:scan --all"
|
|
```
|
|
|
|
#### 19. Check for Errors
|
|
|
|
```bash
|
|
# Check Nextcloud logs
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
docker logs nextcloud --tail 50"
|
|
|
|
# Check for any errors in admin panel
|
|
# Login → Settings → Administration → Logging
|
|
```
|
|
|
|
### Phase 7: Cleanup (Optional)
|
|
|
|
⚠️ **Wait at least 24-48 hours before cleanup to ensure everything works!**
|
|
|
|
#### 20. Remove Old Docker Volume
|
|
|
|
After confirming everything works:
|
|
|
|
```bash
|
|
# Remove old Docker volume (THIS IS IRREVERSIBLE!)
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
docker volume rm nextcloud-data"
|
|
```
|
|
|
|
You'll get an error if any container is still using it (good safety check).
|
|
|
|
## Rollback Procedure
|
|
|
|
If something goes wrong, you can rollback:
|
|
|
|
### Quick Rollback (During Migration)
|
|
|
|
If you haven't removed the old Docker volume:
|
|
|
|
```bash
|
|
# 1. Stop containers
|
|
ansible dev -i hcloud.yml -m shell -a "docker stop nextcloud nextcloud-cron"
|
|
|
|
# 2. Revert docker-compose.yml to use old volume
|
|
# (restore from git or manually edit)
|
|
|
|
# 3. Restart containers
|
|
ansible dev -i hcloud.yml -m shell -a "cd /opt/docker/nextcloud && docker-compose up -d"
|
|
|
|
# 4. Disable maintenance mode
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
docker exec -u www-data nextcloud php occ maintenance:mode --off"
|
|
```
|
|
|
|
### Full Rollback (After Cleanup)
|
|
|
|
If you've removed the old volume but have a backup:
|
|
|
|
```bash
|
|
# 1. Restore from backup to new volume
|
|
# 2. Continue with Phase 5 (restart with new config)
|
|
```
|
|
|
|
## Verification Checklist
|
|
|
|
After migration, verify:
|
|
|
|
- [ ] Nextcloud web interface loads
|
|
- [ ] Can login with existing credentials
|
|
- [ ] All files and folders visible
|
|
- [ ] Can upload new files
|
|
- [ ] Can download existing files
|
|
- [ ] Can edit files (if Collabora Online installed)
|
|
- [ ] Sharing links still work
|
|
- [ ] Mobile apps can sync
|
|
- [ ] Desktop clients can sync
|
|
- [ ] No errors in Nextcloud logs
|
|
- [ ] No errors in admin panel
|
|
- [ ] Volume is mounted in `/etc/fstab`
|
|
- [ ] Volume mounts after server reboot
|
|
|
|
## Common Issues
|
|
|
|
### Issue: "Permission denied" errors
|
|
|
|
**Cause:** Wrong ownership on volume
|
|
|
|
**Fix:**
|
|
```bash
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
chown -R www-data:www-data /mnt/nextcloud-data/data" -b
|
|
```
|
|
|
|
### Issue: "Volume not found" in Docker
|
|
|
|
**Cause:** Docker compose still referencing old volume name
|
|
|
|
**Fix:**
|
|
```bash
|
|
# Check docker-compose.yml has correct mount
|
|
ansible dev -i hcloud.yml -m shell -a "cat /opt/docker/nextcloud/docker-compose.yml | grep mnt"
|
|
|
|
# Should show: /mnt/nextcloud-data/data:/var/www/html/data
|
|
```
|
|
|
|
### Issue: Files missing after migration
|
|
|
|
**Cause:** Incomplete rsync
|
|
|
|
**Fix:**
|
|
```bash
|
|
# Re-run rsync (it will only copy missing files)
|
|
ansible dev -i hcloud.yml -m shell -a "\
|
|
rsync -avh \
|
|
/var/lib/docker/volumes/nextcloud-data/_data/data/ \
|
|
/mnt/nextcloud-data/data/" -b
|
|
```
|
|
|
|
### Issue: Volume unmounted after reboot
|
|
|
|
**Cause:** Not in `/etc/fstab`
|
|
|
|
**Fix:**
|
|
```bash
|
|
# Re-run volume mounting tasks
|
|
ansible-playbook -i hcloud.yml playbooks/deploy.yml --limit dev --tags volume
|
|
```
|
|
|
|
## Post-Migration Benefits
|
|
|
|
After successful migration:
|
|
|
|
- ✅ Can resize storage independently: `./scripts/resize-client-volume.sh dev 200`
|
|
- ✅ Can snapshot data separately from system
|
|
- ✅ Can move data to new server if needed
|
|
- ✅ Better separation of application and data
|
|
- ✅ Clearer backup strategy
|
|
|
|
## Timeline Example
|
|
|
|
Real-world timeline for 10 GB Nextcloud instance:
|
|
|
|
| Step | Duration | Notes |
|
|
|------|----------|-------|
|
|
| Preparation | 5 min | Check status, plan |
|
|
| Create volume (OpenTofu) | 2 min | Automated |
|
|
| Stop services | 1 min | Quick |
|
|
| Mount volume | 2 min | Ansible tasks |
|
|
| Copy data (10 GB) | 5 min | Depends on size |
|
|
| Update config | 2 min | Ansible deploy |
|
|
| Restart services | 2 min | Docker restart |
|
|
| Verification | 10 min | Manual testing |
|
|
| **Total** | **~30 min** | Includes safety checks |
|
|
|
|
## Related Documentation
|
|
|
|
- [Storage Architecture](storage-architecture.md) - Understanding volumes
|
|
- [Deployment Guide](deployment.md) - New deployments with volumes
|
|
- [Client Registry](client-registry.md) - Track migration status
|