Complete Guide: How to Backup and Restore Your Appwrite Self-Hosted Instance

Introduction

Moving or duplicating your Appwrite self-hosted instance doesn’t have to be stressful. Whether you’re migrating to a new server, creating a backup environment, or upgrading your infrastructure, this comprehensive guide will walk you through the entire process of backing up and restoring your Appwrite instance with all data intact.

This guide covers the complete migration from one DigitalOcean droplet to another, but the process works for any server migration.

Prerequisites

  • Source server with working Appwrite instance
  • Target server with Docker and Docker Compose installed
  • SSH access to both servers
  • Sufficient disk space for backup files

Part 1: Creating the Backup

Step 1: Backup Database and Core Components

First, let’s create the backup scripts provided by the Appwrite team. SSH into your source server and navigate to your Appwrite directory.

# Navigate to your Appwrite directory
cd ~/appwrite

# Create backup script
nano backup.sh

Add the following backup script content:

#!/bin/bash

# Backup script for Appwrite
# Stop containers before backup
docker compose down

# Create backup directory
mkdir -p backup

# Backup MariaDB database
docker run --rm \
  -v appwrite_appwrite-mariadb:/dbdata \
  -v $(pwd)/backup:/backup \
  ubuntu tar czf /backup/mariadb.tar.gz -C /dbdata .

# Backup uploads
docker run --rm \
  -v appwrite_appwrite-uploads:/uploads \
  -v $(pwd)/backup:/backup \
  ubuntu tar czf /backup/uploads.tar.gz -C /uploads .

# Backup functions
docker run --rm \
  -v appwrite_appwrite-functions:/functions \
  -v $(pwd)/backup:/backup \
  ubuntu tar czf /backup/functions.tar.gz -C /functions .

# Backup certificates
docker run --rm \
  -v appwrite_appwrite-certificates:/certificates \
  -v $(pwd)/backup:/backup \
  ubuntu tar czf /backup/certificates.tar.gz -C /certificates .

echo "Backup completed successfully!"

Make the script executable and run it:

# Make backup script executable
chmod +x backup.sh

# Run backup
./backup.sh

Step 2: Backup Configuration Files

# Copy configuration files to backup directory
cp .env backup/
cp docker-compose.yml backup/

# Verify backup contents
ls -la backup/

You should see:

  • mariadb.tar.gz
  • uploads.tar.gz
  • functions.tar.gz
  • certificates.tar.gz
  • .env
  • docker-compose.yml

Step 3: Create Restore Script

Create the restore script that you’ll use on the target server:

# Create restore script
nano restore.sh

Add the restore script content:

#!/bin/bash

# Restore script for Appwrite
echo "Starting Appwrite restoration..."

# Stop any running containers
docker compose down

# Start only MariaDB to restore database
docker compose up -d mariadb
echo "Waiting for MariaDB to start..."
sleep 30

# Restore MariaDB data
docker run --rm \
  -v appwrite_appwrite-mariadb:/dbdata \
  -v $(pwd)/backup:/backup \
  ubuntu tar xzf /backup/mariadb.tar.gz -C /dbdata

# Stop MariaDB after restore
docker compose down

# Restore other volumes
echo "Restoring uploads..."
docker run --rm \
  -v appwrite_appwrite-uploads:/uploads \
  -v $(pwd)/backup:/backup \
  ubuntu tar xzf /backup/uploads.tar.gz -C /uploads

echo "Restoring functions..."
docker run --rm \
  -v appwrite_appwrite-functions:/functions \
  -v $(pwd)/backup:/backup \
  ubuntu tar xzf /backup/functions.tar.gz -C /functions

echo "Restoring certificates..."
docker run --rm \
  -v appwrite_appwrite-certificates:/certificates \
  -v $(pwd)/backup:/backup \
  ubuntu tar xzf /backup/certificates.tar.gz -C /certificates

echo "Restoration completed!"
echo "Starting all Appwrite services..."
docker compose up -d

Make it executable:

chmod +x restore.sh

Step 4: Transfer Backup to Target Server

# Create archive of entire backup
tar czf appwrite-backup.tar.gz backup/ restore.sh

# Transfer to target server (replace with your target server details)
scp appwrite-backup.tar.gz root@YOUR_TARGET_SERVER_IP:~/

Part 2: Setting Up the Target Server

Step 1: Create New DigitalOcean Droplet

  1. Log into DigitalOcean console
  2. Create new droplet
  3. Choose “Marketplace” → “Appwrite”
  4. Select your desired size and region
  5. Add SSH keys and create

Step 2: Access New Droplet

# SSH into new droplet
ssh root@YOUR_NEW_DROPLET_IP

# Navigate to Appwrite directory (usually ~/appwrite)
cd ~/appwrite

Part 3: Restoration Process

Step 1: Extract Backup Files

# Extract backup files
tar xzf ../appwrite-backup.tar.gz
mv restore.sh .
chmod +x restore.sh

Step 2: Replace Configuration Files

# Backup original files (just in case)
cp .env .env.original
cp docker-compose.yml docker-compose.yml.original

# Replace with your backed up configurations
cp backup/.env .
cp backup/docker-compose.yml .

Step 3: Critical Step – Remove Existing Volumes

This is the most important step that prevents restoration issues:

# Stop all Appwrite containers
docker compose down

# Remove all existing volumes to prevent conflicts
docker compose down --volumes --remove-orphans

# Optional: Clean up any remaining volumes
docker volume prune -f

Warning: This step deletes all existing data on the target server. Make sure you’re on the correct server!

Step 4: Update Domain Configuration (If Needed)

If your new server has a different IP address, update your .env file:

# Edit .env file
nano .env

# Update these lines with your new server details:
_APP_DOMAIN=your-new-domain.com
_APP_DOMAIN_FUNCTIONS=your-new-domain.com
_APP_DOMAIN_TARGET=your-new-domain.com

# Or for IP-based access:
_APP_DOMAIN=YOUR_NEW_SERVER_IP
_APP_DOMAIN_FUNCTIONS=YOUR_NEW_SERVER_IP
_APP_DOMAIN_TARGET=YOUR_NEW_SERVER_IP
_APP_OPTIONS_FORCE_HTTPS=disabled

Step 5: Run Restoration

# Execute restore script
./restore.sh

Wait for all containers to start up completely (this may take 2-3 minutes).

Step 6: Verify Restoration

# Check container status
docker compose ps

# Check logs for any errors
docker compose logs appwrite

# Test database restoration
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SHOW TABLES;" | head -10

Part 4: Post-Restoration Steps

Step 1: Test Console Access

Navigate to your console:

http://YOUR_NEW_SERVER_IP/console

Step 2: Reset Passwords (If Needed)

If you encounter login issues, reset the console admin password:

# Check console users
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT email FROM _console_users;"

# Reset password for admin user
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "
USE appwrite; 
UPDATE _console_users 
SET password = '\$2y\$12\$k9ijf2QfkJnxl/9i1mGg9.3w8dVpZ8H4VYU6yGZu2rD5W7HvlJqxK' 
WHERE email = 'admin@yourdomain.com';"

Use password: password123 (change it after login)

Step 3: Update DNS (If Using Domain)

If you’re using a domain name, update your DNS records to point to the new server IP.

Step 4: Test Application Functionality

  1. Test user authentication
  2. Verify database collections and documents
  3. Check file uploads
  4. Test any custom functions

Database Verification and Debugging

After restoration, it’s crucial to verify that your database was properly restored. Here’s how to inspect and debug your Appwrite database structure:

Step 1: Verify Database Restoration

# Check if the appwrite database exists
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "SHOW DATABASES;"

# Check what tables were restored
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SHOW TABLES;" | head -20

Step 2: Understanding Appwrite Table Structure

Appwrite uses a specific table naming convention:

  • Console tables: _console_* (admin users, projects, etc.)
  • Project tables: _[PROJECT_ID]_* (your app’s data)
# Check all table patterns
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SHOW TABLES;" | grep -E "^_[0-9]+_|^_console_"

Step 3: Find Your Project ID

The project ID determines your table naming:

# Get your project information
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT \$id, name FROM _console_projects;"

# Example output:
# +------+------------------+
# | $id  | name             |
# +------+------------------+
# | 2    | My App Project   |
# +------+------------------+

If your project ID is 2, your tables will be prefixed with _2_:

  • Users: _2_users
  • Sessions: _2_sessions
  • Collections: _2_database_[DB_ID]_collection_[COLLECTION_ID]

Step 4: Verify User Data

# Check your app users (replace '2' with your actual project ID)
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT email, status, registration FROM _2_users LIMIT 5;"

# Check console admin users
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT email, status FROM _console_users LIMIT 5;"

Step 5: Inspect Collections and Documents

# Find your database and collection tables
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SHOW TABLES LIKE '_2_database_%';"

# Check a specific collection (replace with your actual table name)
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT COUNT(*) as document_count FROM _2_database_2_collection_3;"

# Sample collection data
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT * FROM _2_database_2_collection_3 LIMIT 3;"

Troubleshooting Common Issues

Issue 1: “Table doesn’t exist” Error

Problem: Getting error like Table 'appwrite._1_users' doesn't exist

Diagnosis Steps:

# First, check what tables actually exist
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SHOW TABLES;"

# Look for user tables with different project IDs
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SHOW TABLES;" | grep "_users"

Common Causes:

  1. Wrong Project ID: Your project might be ID 2 instead of 1
  2. Incomplete restoration: Database restoration didn’t complete
  3. Volume conflicts: Old volumes weren’t properly removed

Solution:

# Find correct project ID and use appropriate table names
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT \$id FROM _console_projects;"

# Use the correct table name (e.g., _2_users instead of _1_users)

Issue 2: Login Errors After Restoration (401 Unauthorized)

Problem: Getting 401 errors with message “Invalid credentials”

Diagnosis Steps:

# Check if users exist
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT email, status FROM _2_users WHERE email = 'your@email.com';"

# Check console users
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT email, status FROM _console_users WHERE email = 'admin@yourdomain.com';"

# Look for OpenSSL errors in logs
docker compose logs appwrite | grep -i "openssl\|cipher"

Solution – Reset Passwords:

# Reset console admin password
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "
USE appwrite; 
UPDATE _console_users 
SET password = '\$2y\$12\$k9ijf2QfkJnxl/9i1mGg9.3w8dVpZ8H4VYU6yGZu2rD5W7HvlJqxK' 
WHERE email = 'admin@yourdomain.com';"

# Reset app user password  
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "
USE appwrite; 
UPDATE _2_users 
SET password = '\$2y\$12\$k9ijf2QfkJnxl/9i1mGg9.3w8dVpZ8H4VYU6yGZu2rD5W7HvlJqxK' 
WHERE email = 'user@example.com';"

Temporary password: password123

Issue 3: Empty Database After Restoration

Problem: No tables found or empty tables

Diagnosis:

# Check if MariaDB volume was properly restored
docker volume inspect appwrite_appwrite-mariadb

# Verify backup file integrity
ls -la backup/mariadb.tar.gz
tar -tzf backup/mariadb.tar.gz | head -10

Solution:

# Re-run restoration process
docker compose down --volumes --remove-orphans
./restore.sh

Issue 4: Wrong Project ID in API Calls

Problem: API calls return 401 even with correct credentials

Diagnosis:

# Get your actual project ID
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "USE appwrite; SELECT \$id, name FROM _console_projects;"

Solution: Update your client code with the correct project ID:

const client = new Client()
    .setEndpoint('http://YOUR_SERVER_IP/v1')
    .setProject('2'); // Use the actual project ID from database

Issue 5: Domain/SSL Issues

Problem: SSL errors when accessing via domain.

Solution: Update your .env file with correct domain settings or disable HTTPS for IP access.

Advanced Debugging Commands

Check Database Integrity

# Verify all core tables exist for your project
PROJECT_ID=2  # Replace with your project ID
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "
USE appwrite; 
SELECT 
  CASE 
    WHEN EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = '_${PROJECT_ID}_users') 
    THEN 'EXISTS' ELSE 'MISSING' 
  END as users_table,
  CASE 
    WHEN EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = '_${PROJECT_ID}_sessions') 
    THEN 'EXISTS' ELSE 'MISSING' 
  END as sessions_table,
  CASE 
    WHEN EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = '_console_users') 
    THEN 'EXISTS' ELSE 'MISSING' 
  END as console_table;"

Count Records to Verify Data

# Count important records
PROJECT_ID=2  # Replace with your project ID
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "
USE appwrite;
SELECT 
  (SELECT COUNT(*) FROM _console_users) as console_users,
  (SELECT COUNT(*) FROM _${PROJECT_ID}_users) as app_users,
  (SELECT COUNT(*) FROM _console_projects) as projects;"

Export Sample Data for Comparison

# Export sample data to compare with original
docker compose exec mariadb mysql -u root -p${_APP_DB_ROOT_PASS} -e "
USE appwrite; 
SELECT 'Console Users:' as type, email, status FROM _console_users 
UNION ALL 
SELECT 'App Users:' as type, email, status FROM _2_users 
LIMIT 10;" > restoration_verification.txt

Best Practices

  1. Always test the backup process in a staging environment first
  2. Verify backup integrity before proceeding with restoration
  3. Document your OpenSSL keys and store them securely
  4. Keep multiple backup copies in different locations
  5. Test restoration periodically to ensure your backup process works

Security Considerations

  • Never share your .env file as it contains sensitive keys
  • Change default passwords immediately after restoration
  • Use strong, unique passwords for all accounts
  • Keep your OpenSSL key secure and backed up separately

Conclusion

Following this guide, you should now have a complete duplicate of your Appwrite instance running on a new server. The key steps that ensure success are:

  1. Proper backup of all components (database, files, configurations)
  2. Complete volume removal before restoration
  3. Correct configuration file replacement
  4. Password reset if needed due to encryption key changes

This process allows you to migrate between servers, create staging environments, or simply ensure you have a reliable backup strategy for your Appwrite deployment.

Remember to test your restored instance thoroughly before switching production traffic to it. With these steps, you can confidently migrate your Appwrite instance knowing that all your data and configurations will be preserved.


This guide was tested with Appwrite v1.5.10 on DigitalOcean droplets. Some steps may vary slightly with different versions or hosting providers.

Continue Reading