Post-Tyranny-Tech-Infrastru.../docs/volume-migration.md
Pieter 9eb6f2028a feat: Use Hetzner Volumes for Nextcloud data storage (issue #18)
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>
2026-01-17 21:07:48 +01:00

9.3 KiB

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

# 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:

{
  "Type": "volume",
  "Name": "nextcloud-data",
  "Source": "/var/lib/docker/volumes/nextcloud-data/_data",
  "Destination": "/var/www/html"
}

2. Check Data Size

# 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:

# tofu/terraform.tfvars
clients = {
  dev = {
    # ... existing config ...
    nextcloud_volume_size = 100  # Adjust based on current data size
  }
}

5. Apply OpenTofu Changes

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

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

# 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

# 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

# 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

# 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

# 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

# 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:

volumes:
  - /mnt/nextcloud-data/data:/var/www/html/data

14. Deploy Updated Configuration

# 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

# 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

# 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

# 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

# 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:

# 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:

# 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:

# 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:

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:

# 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:

# 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:

# 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