Here's how to create a backup script for an app running on Lightsail to S3
For our Craft CMS websites, we prefer to run them on lightsail instead of dedicated providers. It's so much faster, cheaper and more flexible.
This tech note describes how to set up backups to s3 for an app running on a lightsail instance. We prefer these over the 'image backups' since we want backups of single applications, in case we have more than one running. And it's cheaper.
This method can be used with little adjustments to backup an application running on any server to S3 of course.
We will:
We'll need to have an S3 bucket to store backups on. You can either create a separate bucket per instance, or create a single bucket that contains multiple backups for multiple purposes.
To reduce management, we'll create a single bucket and name it app-backups (choose your own name).
On bucket creation:
After bucket creation, create a lifecycle rule. This allows us to specify how long old backups are retained (the latest backup is always retained). We can also choose to move old backups to cheaper storage classes, such as glacier.
In this case we'll set up to automatically move old versions of files to a cheaper storage class after some days, and remove old versions completely after X days:
Then, create a folder at the root of the bucket to store backups for your client in. We'll use an alias (normalized or slugified name) for our client, and refer to it as 'CLIENTALIAS' afterwards.
Now we'll create an IAM user and allow it to write to the backup bucket. The user will be installed at the lightsail instance itself, so all apps running on the instance will have access to the resource grants. You can hand over the user to your client too, if you want, so the client can manage backups and other restricted resources by itself.
In this case, we'll create an API only user with the intention that it will give access to all resources for the client in the future, so we'll name it after the client (not the lightsail instance).
First, create a policy for your client.
We'll name it CLIENTALIAS-dobackup-clientpolicy, and it will give access to the
/CLIENTALIAS path in our bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowUserToSeePathInBackupBucket",
"Action": [
"s3:GetBucketLocation",
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::BACKUPBUCKETNAME"
],
"Condition": {
"StringLike": {
"s3:prefix": [
"CLIENTALIAS/*"
]
}
}
},
{
"Sid": "AllowUserToStoreObjectsInCompanyBackupPath",
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::BACKUPBUCKETNAME/CLIENTALIAS/*"
]
}
]
}
Then, create a user with name CLIENTALIAS-instances, attach the CLIENTALIAS-dobackup-clientpolicy policy, and store the access key and secret somewhere safe.
You should test access using the aws cli with commands like:
aws s3 ls s3://BACKUPBUCKETNAME/CLIENTALIAS/
aws s3 cp ./testfile s3://BACKUPBUCKETNAME/CLIENTALIAS/testfile
(temporarily make a profile or add the credentials in AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY environment variables).
Now it's time to prepare the lightsail instance. Login with ssh as the user who should create the backups (for a bitnami image, it would be bitnami).
Make sure the aws cli is installed, we'll use pip because the apt version may err.
sudo apt install python-pip
sudo pip install awscli
and then make the aws credentials the default by entering
aws configure
on the cli and specifying the access key id, secret and region. Test again by something like:
aws s3 ls s3://BACKUPBUCKETNAME/CLIENTALIAS/
Now we're going to install a backups script that will backup a certain directory and backup a single database.
We'll assume that the database root password is stored in ~/bitnami_application_password as raw text.
And that your app base directory is located at:
~/apps/myapp
And the latest release is located at:
~/apps/myapp/current
Place the following file in ~/apps/myapp/backup.sh:
#!/bin/bash -l
PATH="$PATH:/usr/local/bin"
set -e
# replace with your bucket name, target path and database name
S3_BUCKET=app-backups
S3_BASEKEY=CLIENTALIAS/lightsail/LIGHTSAILINSTANVENAME/APPRELEASEALIAS
DBNAME=DATABASE
declare -A FSPATHS
declare -A FSKEYS
# enter for each directory to backup, a target key for s3 and a path to upload
FSKEYS[0]=current
FSPATHS[0]=/opt/bitnami/apps/myapp/current
WORKDIR=$(dirname "$0")/backup/workdir
mkdir -p $WORKDIR
WORKDIR=$(realpath $WORKDIR)
# dump mysql db in workdir
DUMPNAME=$WORKDIR/$DBNAME.sql
DUMPGZNAME=$WORKDIR/$DBNAME.sql.gz
DUMPS3KEY=${S3_BASEKEY}/$DBNAME.sql.gz
echo "DB Backup"
echo "dumping db ${DBNAME} to ${DUMPNAME}"
mysqldump --add-drop-table --single-transaction --disable-keys=true --quick -uroot -p"$(cat ~/bitnami_application_password)" $DBNAME > $DUMPNAME
echo "gzipping to ${DUMPGZNAME}"
gzip -cf $DUMPNAME > $DUMPGZNAME
echo "storing on S3://${S3_BUCKET}/${DUMPS3KEY}"
#aws s3api put-object --storage-class STANDARD_IA --bucket $S3_BUCKET --key $DUMPS3KEY --body $DUMPGZNAME
aws s3 cp --storage-class STANDARD_IA $DUMPGZNAME s3://${S3_BUCKET}/${DUMPS3KEY}
echo "cleaning temp files"
unlink $DUMPGZNAME
echo ""
echo "File sync"
for i in ${!FSPATHS[@]}
do
DIR=${FSPATHS[$i]}
S3KEY=${S3_BASEKEY}/fs/${FSKEYS[$i]}
TARG=s3://${S3_BUCKET}/${S3KEY}
echo "syncing dir ${DIR} to ${TARG}"
aws s3 sync --delete --storage-class STANDARD_IA ${DIR} ${TARG}
done
echo "done"
Then make the script executable by:
chmod +x s3backup.sh
and test it by:
./s3backup.sh
Now we should run the backup with cron.
Do crontab -e, and don't forget to specify the shell and path. Otherwise the aws CLI may not be found.
Create a log directory in your app's directory
(mkdir ~/apps/myapp/log/)
and crontab -e:
```
SHELL=/bin/bash
PATH=$PATH:/usr/local/bin
20 4 * * * ~/apps/myapp/s3backup.sh > ~/apps/myapp/log/s3backup.cron.log 2>&1
```