
I would like to share with you a clear guide with detailed explanation of the purpose of used tools how to migrate your test Docker container with preconfigured WordPress webpage to low cost and low-management AWS solution called AWS Lightsail.
The technology stack that this post covers:
– Docker
– MySQL
– Linux
– AWS
– Cloudflare DNS
GitHub Repo: LINK
Table of Contents
Introduction
I like to prototype my ideas before moving them into production, so I often use my homelab to experiment with a wide range of technologies. This blog was no different—I first set it up inside a Docker container. The biggest advantage? It was completely free.
But since I’m also a cloud enthusiast, I wanted to see how the site would run on Amazon Lightsail, which makes it easy to spin up projects quickly at a low cost.
In this post, I’ll walk you through the process of migrating a WordPress site from Docker to Amazon Lightsail. Along the way, you’ll find short videos demonstrating the steps as well as copy-paste commands and code snippets you can use directly on your machine.
Workflow Graph
Below workflow graph shows the components to successfully complete the migration:
WordPress catalogs explanation
wp-content
= themes, plugins, uploads – main area where WordPress functionality and design are extended.wp-config.php
= database & security settings -because it contains sensitive information, protecting access to this file is crucial.
.htaccess
= server-level rules —incorrect changes can break your entire site.
Docker compose stack
Below you can find Docker compose configuration file that I used for prototyping of WordPress blog:
services:
# MariaDB Database Service
mariadb:
image: mariadb:10.6.4-focal
container_name: mariadb
command: '--default-authentication-plugin=mysql_native_password'
restart: always
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
TZ: Europe/Warsaw
expose:
- 3306
- 33060
volumes:
- mariadb_data:/var/lib/mysql
networks:
- wordpressnet
wordpress:
image: wordpress:latest
ports:
- 8082:80
restart: always
environment:
- WORDPRESS_DB_HOST=mariadb
- WORDPRESS_DB_USER=wordpress
- WORDPRESS_DB_PASSWORD=wordpress
- WORDPRESS_DB_NAME=wordpress
volumes:
- wordpress_data:/var/www/html
networks:
- wordpressnet
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin
restart: always
environment:
PMA_ARBITRARY: 1
TZ: Europe/Warsaw
ports:
- "8081:80"
depends_on:
mariadb:
condition: service_started
networks:
- wordpressnet
networks:
wordpressnet:
driver: bridge
name: wordpress_bridge
ipam:
config:
- subnet: 172.33.0.0/24
volumes:
mariadb_data:
wordpress_data:
Checking running Docker compose stack
# 1.Check existing docker-compose stack
docker-compose ls | grep wordpress
# 2.Check running docker compose processes
docker-compose ps
# 3.Check running docker containers
docker ps -a | egrep 'wordpress|phpmyadmin|mariadb'
## 1
Docker-host:~/wordpress_stack-compose$ docker-compose ls | grep wordpress
wordpress_stack-compose running(3) /home/dwojciech/wordpress_stack-compose/docker-compose.yaml
## 2
Docker-host:~/wordpress_stack-compose$ docker-compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
mariadb mariadb:10.6.4-focal "docker-entrypoint.s…" mariadb 5 weeks ago Up 3 days 3306/tcp, 33060/tcp
phpmyadmin phpmyadmin/phpmyadmin "/docker-entrypoint.…" phpmyadmin 5 weeks ago Up 3 days 0.0.0.0:8081->80/tcp, [::]:8081->80/tcp
wordpress_stack-compose-wordpress-1 wordpress:latest "docker-entrypoint.s…" wordpress 5 weeks ago Up 3 days 0.0.0.0:8082->80/tcp, [::]:8082->80/tcp
## 3
Docker-host:~/wordpress_stack-compose$ docker ps -a | egrep 'wordpress|phpmyadmin|mariadb'
4f93c2b0f2a7 phpmyadmin/phpmyadmin "/docker-entrypoint.…" 5 weeks ago Up 3 days 0.0.0.0:8081->80/tcp, :::8081->80/tcp
7a56fe8d7896 mariadb:10.6.4-focal "docker-entrypoint.s…" 5 weeks ago Up 3 days 3306/tcp, 33060/tcp
c2befafabfd1 wordpress:latest "docker-entrypoint.s…" 5 weeks ago Up 3 days 0.0.0.0:8082->80/tcp, :::8082->80/tcp
Docker Compose Stack
You can see above that we can distinguish 3 running containers in docker compose stack:
- mariadb (database)
- wordpress
- phpmyadmin (database manager)
IP address schema
All of the containers in this stack are running in admin-defined network 172.33.0.0/24.
Volumes
mariadb & wordpress have persistent container volumes, name accrodingly mariadb_data & wordpress_data
Backup Docker containers data
Step1. Create wp-backup folder
# Create a backup folder and navigate into it
mkdir -p ~/wp-backup && cd ~/wp-backup
Step2. Create mariaDB SQL dump
# Dump the DB from the mariadb container
docker exec -i mariadb \
mysqldump -u root -psecret --databases wordpress \
--single-transaction --quick --routines --events --triggers \
> wordpress.sql
Step3. Export WordPress Files and Config
Check existing volume names
Verify what are existing persistent volume names created by wordpress_stack run command.
docker volume ls | grep wordpress_stack
dwojciech@Docker-host:~/wp-backup$ docker volume ls | grep wordpress_stack
local wordpress_stack-compose_mariadb_data
local wordpress_stack-compose_wordpress_data
In order to copy wordpress media and configuration we can use 2 methods:
Method 1. Run temporary helper container
Method 2. Use docker cp command
Method 1 is quite harder but if you are big fan of containers you will manage it without any problems. 🙂
Let me walk you through both methods.
Method1. Run temporary helper container
Copy wp-content
# Archive wp-content from the named volume to the current host dir (~/wp-backup)
docker run --rm --network none --read-only \
-v wordpress_stack-compose_wordpress_data:/var/www/html:ro \
-v "$PWD":/backup \
alpine sh -c 'cd /var/www/html && tar czf /backup/wp-content.tgz wp-content'
We start a short-lived alpine container just to read the volume and write a tar file, then throw that helper away.
Syntax clarifications:
– – rm only removes the temporary helper container after it finishes.
It does not stop or remove your running WordPress, MariaDB, or phpMyAdmin containers, and it does not delete the volume.
– – network none completely isolates container from networking, who needs network for temporary container ?!
– – read-only means – temporary container cannot modify any files inside its root filesystem (more security safeguard)
-v allows to mount a volumes to temporary container
volume1 (persistent): wordpress_stack-compose_wordpress_data :maps to: /var/www/html
volume2 (helper): maps ~/wp-backup/ to /backup of alpine container
Copy wp-config
# Copy wp-config.php into the current host dir (~/wp-backup)
docker run --rm --network none --read-only \
-v wordpress_stack-compose_wordpress_data:/var/www/html:ro \
-v "$PWD":/backup \
alpine sh -c 'cp /var/www/html/wp-config.php /backup/wp-config.php'
Copy .htaccess
# Copy .htaccess into the current host dir (~/wp-backup)
docker run --rm --network none --read-only \
-v wordpress_stack-compose_wordpress_data:/var/www/html:ro \
-v "$PWD":/backup \
alpine sh -c 'cp /var/www/html/.htaccess /backup/.htaccess'
Method2. Use docker cp command
# docker cp syntax
docker cp <Wordpress CONTAINER_NAME or ID>:SOURCE_PATH DEST_PATH
Copy wp-content
#Example
cd ~/wp-backup
docker cp c2befafabfd1:/var/www/html/wp-content .
Copy wp-config.php
#Example
cd ~/wp-backup
docker cp c2befafabfd1:/var/www/html/wp-config.php .
Copy .htaccess
#Example
cd ~/wp-backup
docker cp c2befafabfd1:/var/www/html/.htaccess .
Step4. Organize Backup Files into wp-backup catalog
Now it’s time to prepare migration notes that will help us to identify specific Docker parameters to be replaced in the Lightsail instance configuration.
touch migrationNotes.txt
echo Current WordPress URL:http://$(hostname -I | awk '{print $1}'):$(docker port c2befafabfd1 | grep "0.0.0.0" | awk -F: '{print $2}') > migrationNotes.txt
cat migrationNotes.txt
#Example
## Current WordpPress URL:http://nexthopcloud.com
Your backup catalog should already have 5 objects:
- wordpress.sql
- wp-config.php
- wp-content
- .htaccess
- migrationNotes.txt
# Verify that all files are within backup folder
cd ~/wp-backup
tree -a -L 1
├── .htaccess
├── migrationNotes.txt
├── wordpress.sql
├── wp-config.php
└── wp-content
2 directories, 4 files
Now it’s time to archive wp-backup catalog
# Archive catalog
## -z: compress archive with gzip;
## -c: create a new archive;
## -v: verbose mode to show progress
## -f: speciy archive file name
tar -zcvf wp-backup.tar.gz wp-backup
# Check file spaces for comparison
du -sh wp-backup
#Example: 317M wp-backup
du -sh wp-backup.tar.gz
#Exammple: 89M wp-backup.tar.gz
Configure Lightsail
Lightsail Instance Sizing
When choosing a Lightsail instance size, it’s usually best to start small and scale up as your needs grow. Beginning with a smaller instance keeps costs low and makes it easier to adjust resources later. If your application outgrows the initial setup, you can seamlessly move to a larger Lightsail instance or even migrate to more advanced AWS services such as EC2 or ECS for greater flexibility and performance.
1GB memory, 2vCPU, 40GB SSD is sufficient + notice the First 90 days are free!
Instance Static IP
As a prerequisite create and attach static public IP to your Lightsail Intance.
AWS documentation clearly explains this process, check out here:
Amazon Lightsail User Guide Create and attach a static IP to your Lightsail instance
# Use api request to get your current public IP address
curl https://ipinfo.io/ip
# -> save Public IP result
Point DNS A record to Public IP of Lightsail Instance
The prerequisite is to have a domain already purchased. In my case I registered a domain in Cloudflare.
Here is step-by-step procedure of A Record configuration:
1. Log in to Cloudflare and open your domain’s dashboard.
2. Go to the DNS section.
3. Click Records.
4. Click Add Record.
5. Choose A as the record type.
6. In the Name field, enter @
(for the root domain, e.g., nexthopcloud.com
).
7. In the IPv4 address field, paste the Public IP address of your Lightsail instance.
8. Set Proxy status to Proxied (orange cloud) if you want Cloudflare’s caching, CDN, and security features.8. Click Save.
Step5. Copy Backup to Lightsail
#Modify key privileges
chmod 400 ~/.ssh/LightsailDefaultKey-eu-central-1.pem
# Login to Lightsail instance
## where PublicIP = PublicIP of your Lightsail instance
ssh -i ~/.ssh/LightsailDefaultKey-eu-central-1.pem bitnami@<PublicIP>
Download Default Key from Lightsail
Upload private key to Docker-host
I’m using WinSCP to upload downloaded .pem key to Docker-host instance.
Private key should be located in hidden folder ~/.ssh/ and have strictly configured privileges.
You should be able to login and see similar welcome screen:
Copy wp-backup catalog from Docker-host to Lightsail instance.
Docker-host path: ~/wp-backup.tar.gz
Lightsail path: /opt/bitnami/wordpress/wp-backup.tar.gz
# Copy folder from Docker-host to Lightsail instance using scp protocol
## where PublicIP = PublicIP of your Lightsail instance
scp -i ~/.ssh/LightsailDefaultKey-eu-central-1.pem \
~/wp-backup.tar.gz bitnami@<PublicIP>:/opt/bitnami/wordpress/wp-backup.tar.gz
# Output
## wp-backup.tar.gz 100% 89MB 12.0MB/s 00:07
Step6.Extract WordPress Files
In the next step we will extract sql database dump as well as WordPress configuration files fom wp-backup.tar.gz archive.
# Extract catalog
## -x: extract
## -v: verbose mode to show progress
## -f: specify archite file name
sudo tar -xvf wp-backup.tar.gz
cd wp-backup
ls -l
# Example
## total 6968
## -rw-rw-r-- 1 bitnami bitnami 47 Sep 14 11:18 migrationNotes.txt
## -rw-rw-r-- 1 bitnami bitnami 7115469 Sep 9 16:45 wordpress.sql
## -rw-r--r-- 1 bitnami bitnami 5919 Jul 28 17:16 wp-config.php
##drwxr-xr-x 7 bitnami bitnami 4096 Sep 15 17:07 wp-content
#Prepare paths to important migration files
sql="/opt/bitnami/wordpress/wp-backup/wordpress.sql"
echo $sql
wpcontent="/opt/bitnami/wordpress/wp-backup/wp-content"
echo $wpcontent
wpconfig="/opt/bitnami/wordpress/wp-backup/wp-config.php"
echo $wpconfig
Step7. Recreate WordPress Database
Check your database password and perform first login:
# show the Bitnami app credentials (contains the DB/root password)
sudo cat /home/bitnami/bitnami_credentials
# password value should be displayed
#Connect to your DB
/opt/bitnami/mariadb/bin/mariadb -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 5568
Server version: 11.8.2-MariaDB Source distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current
#Verify configured databases
SHOW DATABASES;
# Remove existing bitnami_wordpress database
DROP DATABASE bitnami_wordpress;
# Create bitnami_wordpress database from the scratch
CREATE DATABASE bitnami_wordpress;
# Exit mysql session
EXIT;
Step8. Import Database Backup
# Make a backup of your original dump first
cp /opt/bitnami/wordpress/wp-backup/wordpress.sql /opt/bitnami/wordpress/wp-backup/wordpress_original.sql
# Edit the dump file
sudo nano /opt/bitnami/wordpress/wp-backup/wordpress.sql
Changes to make in wordpress.sql dump file
Find all ‘wordpress‘ phrases:
--
-- Current Database: `wordpress`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `wordpress` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `wordpress`;
Replace with bitnami_wordpress:
--
-- Current Database: `bitnami_wordpress`
--
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `bitnami_wordpress` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `bitnami_wordpress`;
## Verify Changes
head -25 wordpress.sql | grep -E "(Database|CREATE DATABASE|USE)"
Import wordpress sql dump into mariadb on Lightsail instance
# Import modified dump file
/opt/bitnami/mariadb/bin/mariadb -u root -p < $sql
# Verify the import
/opt/bitnami/mariadb/bin/mariadb -u root -p
SHOW DATABASES;
USE bitnami_wordpress;
SHOW TABLES;
SELECT option_name, option_value FROM wp_options WHERE option_name IN ('siteurl', 'home');
EXIT;
Step9. Import WordPress Content
# Backup current wp-content
sudo cp -r /opt/bitnami/wordpress/wp-content /opt/bitnami/wordpress/wp-content-original
# Remove default content
sudo rm -rf /opt/bitnami/wordpress/wp-content/uploads
sudo rm -rf /opt/bitnami/wordpress/wp-content/plugins/*
sudo rm -rf /opt/bitnami/wordpress/wp-content/themes/*
# Copy your content including hidden files
sudo cp -r $wpcontent/. /opt/bitnami/wordpress/wp-content/
# Fix permissions
sudo chown -R bitnami:daemon /opt/bitnami/wordpress/wp-content
sudo chmod -R 755 /opt/bitnami/wordpress/wp-content
sudo find /opt/bitnami/wordpress/wp-content -type f -exec chmod 644 {} \;
Step10. Update wp-config.php Settings
# Update WordPress URLs in wp-config.php
nano -l /opt/bitnami/wordpress/wp-config.php
Instead of default values add your domain DNS address in lines 106 & 107.
Example:
Step11. Update URLs in the Database
Now it’s time to replace old Docker host IP and port with new DNS name in wordpress database. You are going to use wp search-replace utility.
cd /opt/bitnami/wordpress
# sudo wp search-replace 'old-string' 'new-string' --allow-root
## First Dry Run
sudo wp search-replace 'http://10.130.0.17:8082' 'https://nexthopcloud.com' --all-tables --allow-root --dry-run
# Replacement
sudo wp search-replace 'http://10.130.0.17:8082' 'https://nexthopcloud.com' --all-tables --allow-root
## Check if any old URLs remained
sudo wp db search '10.130.0.17' --allow-root
# Verify if the values are updated
sudo wp option get siteurl --allow-root
sudo wp option get home --allow-root
Perform Direct Database Check and Fix (if needed)
# Login to DB
/opt/bitnami/mariadb/bin/mariadb -u root -p bitnami_wordpress
#SQL
-- Find all references to the old URL
SELECT * FROM wp_options WHERE option_value LIKE '%10.130.0.17:8082%';
SELECT * FROM wp_postmeta WHERE meta_value LIKE '%10.130.0.17:8082%';
SELECT * FROM wp_posts WHERE post_content LIKE '%10.130.0.17:8082%';
-- Update any found references (be careful with serialized data)
UPDATE wp_options SET option_value = REPLACE(option_value, 'http://10.130.0.17:8082', 'https://nexthopcloud.com') WHERE option_value LIKE '%10.130.0.17:8082%';
UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'http://10.130.0.17:8082', 'https://nexthopcloud.com') WHERE meta_value LIKE '%10.130.0.17:8082%';
UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://10.130.0.17:8082', 'https://nexthopcloud.com') WHERE post_content LIKE '%10.130.0.17:8082%';
Force Elementor to Regenerate everything
## Clear all Elementor data and force regeneration
wp option delete elementor_css_print_method --allow-root
wp option delete elementor_frontend_config --allow-root
wp option update elementor_clear_cache 1 --allow-root
## Force WordPress to regenerate permalinks
wp rewrite flush --allow-root
## Clear all caches
wp cache flush --allow-root
Step12. Restart Apache Service
# Restart Apache to ensure all changes take effect
sudo /opt/bitnami/ctlscript.sh restart apache
Accessibility Test
Ensure you remember credentials used previously on Docker container WordPress as the Lightsail default credentials will no longer work here.
In addition, several effective measures should be implemented to protect login page agains unuauthorized access and brute force attacks:
- Use Strong, unique passwords combining uppercase, lowercase letters, numbers, and special characters.
- Change the default WordPress login URL from common /wp-login.php or /wp-admin to custom URL, which helps reduce bot attacks.
- Limit login attempts to block IP addresses temporarily after repeated failed attempts, preventing brute force attacks.
- Enable two-factor authentication (2FA) requiring an additional verification step beyond the password for stronger security.
- Use SSL/TLS encryption for secure login data transmission.
- Harden the wp-config.php file and remove or disable unused user accounts, plugins, and themes.
Key takeaways
- Docker-based WordPress migrated to AWS Lightsail.
- DNS + static IP taken care of.
- Minimal startup cost, reasonable performance.