Migrate Your Free Tier Library to a Separate Data Volume

One-time migration guide for existing Free tier users to move library data from the root volume to a new persistent EBS data volume. Required before upgrading to an AMI released after 2026-05-27.

Migrate Your Free Tier Library to a Separate Data Volume

⚠️ Read before proceeding

This guide describes a process that can cause data loss or extended downtime if steps are missed or executed out of order.

Before you start:

  • Back up your data and verify the backup is complete and restorable.
  • Test the full process on a non-production environment before touching your live library.
  • Schedule a maintenance window and notify users if this affects a production system.

Not confident? KohaSupport can guide you through migrations and upgrades. Contact us for professional assistance →

Starting with AMIs released after 2026-05-27, the Free tier uses a separate persistent EBS data volume for your library data — the same model as Standard tier. Your MySQL database, Koha configuration, and SSL certificates live on this separate volume, which survives instance replacements and AMI upgrades.

If your stack was created before this date, your data is still on the root volume and you need to perform this one-time migration before upgrading your AMI. If you upgrade without migrating, your library catalog will be replaced by a fresh empty Koha installation.


What changes

  Before (legacy) After (migration complete)
Root volume 20 GiB — OS + Koha data 10 GiB — OS only
Data volume None 20 GiB (or larger) separate EBS vol
Data survives instance replacement? ❌ No ✅ Yes — DeletionPolicy: Retain
AMI upgrades Data lost Safe — swap instance, keep data

This guide is for CloudFormation-based deployments only. If you launched your Koha server directly from the EC2 Launch Wizard (without CloudFormation), the steps below — which use CloudFormation stack updates and Auto Scaling Groups — do not apply. For a direct EC2 deployment, the equivalent process is:

  1. Create a new EBS volume in the same Availability Zone as your instance
  2. Attach it as /dev/sdb
  3. Format and mount it: sudo mkfs.ext4 /dev/xvdb && sudo mkdir -p /kohadata && sudo mount /dev/xvdb /kohadata
  4. Copy Koha data to it and update /etc/fstab for persistence
  5. Reconfigure Koha to use /kohadata

This is an advanced procedure. Contact KohaSupport if you need assistance with a direct EC2 data volume migration.

Before you start

  • This migration requires approximately 30–45 minutes and a brief period of Koha downtime
  • You need AWS Console or CLI access
  • Run all instance commands via EC2 Instance Connect or Session Manager (SSM)

Step 1 — Take a safety backup

Connect to your instance and run koha-dump:

sudo koha-dump library
ls -lh /var/spool/koha/library/

Note the backup filename (e.g. library-2026-05-29.tar.gz). This stays on the root volume of the current instance as a safety net — if the migration fails before Step 4, you can restore from it.


Step 2 — Update your CloudFormation stack to add the data volume

This step creates the new persistent EBS volume and registers it in SSM. Your current instance keeps running during this step.

  1. Go to CloudFormation → select your stack → Update stack
  2. Choose Replace existing template
  3. Enter the new Free tier template URL (from your AWS Marketplace subscription launch page)
  4. Set EBSVolumeSize to your desired data volume size (default: 20 GiB)
  5. Leave all other parameters unchanged → Update stack
  6. Wait for UPDATE_COMPLETE

Confirm the volume was created: Outputs tab → DataVolumeId — note this value.


Step 3 — Copy your library data to the new volume

Run this on your currently running instance. It attaches the new empty volume, copies your Koha data onto it, and marks it as initialised so the new AMI skips its first-boot copy.

Connect via Session Manager or EC2 Instance Connect and run:

# IMDSv2 helper (required on instances with HttpTokens=required)
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
get_meta() { curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" "http://169.254.169.254/latest/meta-data/$1"; }

INSTANCE_ID=$(get_meta instance-id)
REGION=$(get_meta placement/region)
export AWS_DEFAULT_REGION="$REGION"

STACK=$(aws ec2 describe-tags \
  --filters "Name=resource-id,Values=$INSTANCE_ID" \
            "Name=key,Values=aws:cloudformation:stack-name" \
  --query 'Tags[0].Value' --output text)

VOLUME_ID=$(aws ssm get-parameter \
  --name "/koha/$STACK/data-volume-id" \
  --region "$REGION" --query 'Parameter.Value' --output text)
echo "Attaching $VOLUME_ID to $INSTANCE_ID"

aws ec2 attach-volume \
  --volume-id "$VOLUME_ID" --instance-id "$INSTANCE_ID" \
  --device /dev/xvdg --region "$REGION"

VOL_SERIAL=$(echo "$VOLUME_ID" | tr -d '-')
DEVICE=""
for i in $(seq 1 60); do
  DEVICE=$(lsblk --output NAME,SERIAL --noheadings 2>/dev/null \
    | awk -v s="$VOL_SERIAL" '$2==s {print "/dev/"$1; exit}')
  [ -z "$DEVICE" ] && [ -b "/dev/xvdg" ] && DEVICE="/dev/xvdg"
  [ -n "$DEVICE" ] && [ -b "$DEVICE" ] && break
  sleep 3
done
echo "Block device: $DEVICE"

sudo mkfs.ext4 -L kohadata "$DEVICE"
sudo mkdir -p /mnt/kohadata-migration
sudo mount "$DEVICE" /mnt/kohadata-migration

sudo systemctl stop mysql 2>/dev/null || sudo systemctl stop mariadb 2>/dev/null
sudo rsync -a /var/lib/mysql/ /mnt/kohadata-migration/mysql/
sudo rsync -a /etc/koha/sites/ /mnt/kohadata-migration/koha-sites/ 2>/dev/null || true
sudo rsync -a /var/lib/koha/ /mnt/kohadata-migration/koha-lib/ 2>/dev/null || true
[ -d /etc/letsencrypt ] && sudo rsync -a /etc/letsencrypt/ /mnt/kohadata-migration/letsencrypt/ || true

sudo touch /mnt/kohadata-migration/.kohadata.initialized
echo "=== Migration copy complete ==="
sudo ls -la /mnt/kohadata-migration/

sudo systemctl start mysql 2>/dev/null || sudo systemctl start mariadb 2>/dev/null

sudo umount /mnt/kohadata-migration
aws ec2 detach-volume --volume-id "$VOLUME_ID" --region "$REGION"
echo "Volume detached — ready for instance replacement"

Wait ~30 seconds, then confirm the volume is detached:

aws ec2 describe-volumes --volume-ids "$VOLUME_ID" \
  --query 'Volumes[0].State' --output text --region "$REGION"
# Expected: available

Step 4 — Replace the instance

Your library data is now on the data volume. Terminate the old instance so the ASG launches a replacement with the new AMI.

Because DisableApiTermination is set, use the Auto Scaling Group — not direct EC2 terminate.

Via AWS Console:

  1. EC2Auto Scaling Groups → find <stack-name>-asg
  2. Instance Management tab → select your instance
  3. ActionsTerminateDo not decrease desired capacity → confirm

Via AWS CLI:

STACK=<your-stack-name>
REGION=us-east-1

ASG=$(aws cloudformation describe-stack-resources \
  --stack-name "$STACK" \
  --query "StackResources[?ResourceType=='AWS::AutoScaling::AutoScalingGroup'].PhysicalResourceId" \
  --output text --region "$REGION")

INSTANCE=$(aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names "$ASG" \
  --query "AutoScalingGroups[0].Instances[0].InstanceId" \
  --output text --region "$REGION")

aws autoscaling terminate-instance-in-auto-scaling-group \
  --instance-id "$INSTANCE" \
  --no-should-decrement-desired-capacity \
  --region "$REGION"

Wait 5–8 minutes. The new instance boots, attaches the data volume, sees the .kohadata.initialized marker file, and starts Koha using your migrated library data.


Step 5 — Verify

Connect to the new instance and check:

# Data volume mounted at /kohadata
df -h | grep kohadata

# MySQL running from data volume
sudo mysql -e "SELECT @@datadir;"

# Plack running
sudo koha-plack --status library

# Confirm migration boot (should NOT see "First boot: initialising data volume")
sudo grep "upgrade boot\|First boot\|kohadata.initialized" /var/log/koha-userdata.log

Expected:

# df:
/dev/nvme1n1      20G   ...   /kohadata

# @@datadir:
/kohadata/mysql/

# userdata log:
=== AMI upgrade boot: existing library data detected — skipping initialisation ===

Open your Koha OPAC and Staff Interface to confirm your catalog is intact.


Rollback

Before Step 4 (old instance still running): Run sudo umount /mnt/kohadata-migration, detach the volume, restart MySQL. Your data is still on the root volume.

After Step 4: The data volume has your migrated data. If Koha is not starting, check /var/log/koha-userdata.log and restart services: service memcached restart && koha-plack --restart library.


EBS volume size

The EBSVolumeSize parameter controls the data volume size (not the root — root is always 10 GiB from the AMI). Default: 20 GiB. You can increase this later via EC2 → Volumes → Modify volume, but cannot decrease it.

Next Steps

Was this article helpful?

Thanks for your feedback!