Skip to content

Disaster Recovery & Restore Runbook

Target: Cloudflare R2 bucket (cortex-backups)
Endpoint: 32f98e32368b62781593f92a61020532.r2.cloudflarestorage.com
Backup Matrix & Architecture: See docs/backup.md


Phase 1: Bare-Metal Host Setup

If the host fails or you migrate to new hardware:

  1. Install Base OS & Docker: Install Ubuntu Server (or Debian), Docker Engine, and the Docker Compose plugin.
  2. Clone the Cortex Repository:
    git clone git@github.com:taskfork/cortex.git /home/tforkan/docker
    cd /home/tforkan/docker
    
  3. Recreate Stack Secrets (.env files): Retrieve stack environment variables and API keys from your password manager (Bitwarden / 1Password) and place .env files in each service directory.

Phase 2: Connect Kopia to Cloudflare R2

  1. Start Kopia:
    cd /home/tforkan/docker/kopia
    docker compose up -d
    
  2. Connect to Existing R2 Repository: Open the Kopia Web UI at http://<server-ip>:51515 (or execute via CLI):
    docker exec -it kopia kopia repository connect s3 \
      --bucket=cortex-backups \
      --endpoint=32f98e32368b62781593f92a61020532.r2.cloudflarestorage.com \
      --access-key=<R2_ACCESS_KEY_ID> \
      --secret-access-key=<R2_SECRET_ACCESS_KEY>
    
    Enter your Kopia Master Passphrase when prompted.
  3. Verify Snapshots:
    docker exec kopia kopia snapshot list --all
    

Phase 3: Service-by-Service Restoration

1. Immich (Photos & Database)

  1. Restore Immich database dump and photo library from Kopia:
    docker exec kopia kopia restore <snapshot-id-for-immich-db-dumps> /tmp/immich_restore_db
    docker exec kopia kopia restore <snapshot-id-for-immich-library> /home/tforkan/docker/immich/library/library
    
  2. Start the Immich Postgres container:
    cd /home/tforkan/docker/immich
    docker compose up -d database
    
  3. Import the latest .sql.gz dump:
    zcat /tmp/immich_restore_db/latest.sql.gz | docker exec -i immich-postgres psql -U postgres -d immich
    
  4. Start the rest of the Immich stack:
    docker compose up -d
    rm -rf /tmp/immich_restore_db
    

2. Paperless-ngx (Documents & Database)

  1. Restore Paperless volume data and database dumps:
    docker exec kopia kopia restore <snapshot-id-for-paperless-data> /tmp/paperless_data
    docker exec kopia kopia restore <snapshot-id-for-paperless-media> /tmp/paperless_media
    docker exec kopia kopia restore <snapshot-id-for-paperless-db-dumps> /tmp/paperless_db_dumps
    
  2. Copy files to target volume mounts:
    docker cp /tmp/paperless_data/. paperless-webserver:/usr/src/paperless/data/
    docker cp /tmp/paperless_media/. paperless-webserver:/usr/src/paperless/media/
    
  3. Restore Postgres DB dump into paperless-db:
    zcat /tmp/paperless_db_dumps/latest.sql.gz | docker exec -i paperless-db psql -U paperless -d paperless
    
  4. Restart Paperless stack:
    cd /home/tforkan/docker/paperless-ngx
    docker compose restart
    rm -rf /tmp/paperless_data /tmp/paperless_media /tmp/paperless_db_dumps
    

3. AdventureLog (Trips, Media & Database)

  1. Restore AdventureLog volume data and dumps from Kopia:
    docker exec kopia kopia restore <snapshot-id-for-adventurelog-db-dumps> /tmp/adventurelog_db_dumps
    docker exec kopia kopia restore <snapshot-id-for-adventurelog-media> /tmp/adventurelog_media
    
  2. Copy media files to target container volume:
    docker cp /tmp/adventurelog_media/. adventurelog:/code/media/
    
  3. Restore Postgres DB dump into adventurelog-db:
    zcat /tmp/adventurelog_db_dumps/last/database-latest.sql.gz | docker exec -i adventurelog-db psql -U adventure -d database
    
  4. Restart AdventureLog stack:
    cd /home/tforkan/docker/adventurelog
    docker compose restart
    rm -rf /tmp/adventurelog_db_dumps /tmp/adventurelog_media
    

4. Configuration Directories (Bind Mounts)

For bind-mounted configuration directories, restore snapshots directly to their folders on the host:

# Home Assistant
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/homeassistant/config

# Authelia
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/authelia/config
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/authelia/secrets

# Jellyfin
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/jellyfin/config

# Seerr
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/seerr/config

# Yamtrack
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/yamtrack/db

# Glance
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/glance/config

# Pi-hole
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/pihole/etc-pihole
docker exec kopia kopia restore <snapshot-id> /home/tforkan/docker/pihole/etc-dnsmasq.d

Start the restored stacks:

docker compose up -d


5. Named Docker Volumes (Homebox & Uptime Kuma)

# 1. Create target volumes if they don't exist
docker volume create homebox_homebox-data
docker volume create uptime-kuma_uptime-kuma

# 2. Restore Kopia snapshots directly into volume paths
docker exec kopia kopia restore <snapshot-id-homebox> /data/homebox_data
docker exec kopia kopia restore <snapshot-id-kuma> /data/uptime_kuma_data

# 3. Start containers
cd /home/tforkan/docker/homebox && docker compose up -d
cd /home/tforkan/docker/uptime-kuma && docker compose up -d