How To Migrate WordPress Websites to WP Engine

How To Migrate WordPress Websites to WP Engine

This blog post outlines how to migrate a WordPress website from any hosting provider to WP Engine hosting account without access to source FTP site. The post focuses around utilizing the WP Engine Migration Assistant Plugin. WP Engine offers a specialized plug in to make WordPress migrations a pretty simple from one hosting provider to another. In this example it doesn’t matter what your hosting provider is, this plug in should work with any of them. And best of all, you don’t need access to the source FTP site to do the migration.

Managed Hosting for Business Operations

A large part of my business is providing an array of WordPress hosting services that cover all the backend configurations, including SSL installations, plugin updates, and server-side optimizations for my clients’ websites. This frees them up to focus on their business and what they do best instead of having to struggle working on a system they dont’ understand nor do they care about understanding it. Part of my service offering is migrating WordPress sites from their old hosting provider to my own managed WP Engine hosting service.

Let’s get started.


  • Access to the existing WordPress site’s admin panel
  • WP Engine account with appropriate permissions

Migration Using WP Engine Migration Assistant Plugin

This is how you utilize the WP Engine Migration Assistant Plug in to migrate just about any WordPress site to your WP Engine hosting account.

Step 1: WP Engine Plugin Installation

  1. Log into the existing/source WordPress site.
  2. Navigate to Plugins -> Add New on the WordPress Dashboard.
  3. Search for WP Engine Automated Migration. Or you can download the plug in here.
  4. Click Install and then Activate.

Step 2: Retrieve Migration Credentials

  1. Log in to the WP Engine User Portal.
  2. Create the new site environment if you have not already done so.
  3. Navigate to new WP Engine site and click Migrations in the left side navigation menu.
  4. You’ll see a screen that looks like this one below. Here you see the site properties that you’ll need to input into the source/existing website migration plug in. You can generate the migration SFTP credentials here if you have not already done so.
WP Engine Migrate website

Step 3: Plugin Configuration

  1. On the existing/source WordPress Dashboard, go to WP Engine Migration from the left side navigation.
  2. Input the migration credentials from Step 2.
  3. Click Start Migration.

Step 4: Monitoring and Verification

  1. Monitor migration status in the WP Engine User Portal.
  2. You’ll receive a confirmation email upon successful migration.
WP Engine migration status

Step 5: DNS Update

  1. Now update the DNS records to point to WP Engine’s IP address or CNAME records.


This blog post provides a step by step approach for migrating a WordPress website from any provider to WP Engine utilizing the WP Engine Migration plug in.

For more specialized migration services or hosting solutions, feel free to contact me. Happy migrating!

Moving Docker SQL Server databases between machines

Moving Docker SQL Server databases between machines

I’m in the middle of migrating all my apps and databases to a new Macbook Air machine. Since I do all my .Net Core development on MacOS I use Docker containers to manage any SQL Server databases that I’m working with. In this blog post I’ll show how I migrated my SQL Server databases between machines and Docker containers.

Create a database backup on machine 1

I used Azure Data Studio to manage SQL Server databases from my Mac. Azure Data studio is a fine substitute for SQL Server Management Studio, but it lakes the majority of the features. Though I’ll take the lack of features for the ability to development on my macOS machine any day.

To back up a database from Azure Data Studio, connect to your server, navigate to the database and right click the database. Then select the Backup option from the drop down menu. Select the options you need and click Backup. Take not of the Backup files location as you’re going to need this to copy the file from the Docker container to your local machine. Here my backup copy will be made in /var/opt/mssql/data folder within the current Docker container.

Azure studio backup
Use Azure Data Studio to backup a database

Copy the database backup to a central location

Now using the Docker command docker cp copy the the database backup file from the Docker container to your local machine and then to a central location where you can access the backup file from the destination machine.

  1. Open the Docker dashboard on the source machine, locate the SQL Server container, then copy the container id.
Locate SQL Server container
Copy the container id

2.  Open Terminal and execute the docker cp command with the following parameters: {yourContainerId}:{containerBackupFileLocation} {destinationFolder}

docker cp cbd5323d5caf78c9b7434ff46eccbba0b5c20e2e4807d71fd83a6394372fc1c0:/var/opt/mssql/data/Machine-1-2023818-11-11-11.bak ~/code

3.  This will copy the .bak file from the previous step into a folder called code on your local machine. Make sure the destination folder code exists before you run this command.

4. Transfer the .bak to a central location that the new machine can access. Something like Dropbox or shared filed location is good. And then download the file to the new machine.

5. Run the following command from the new machine to create the backup folder within the new Docker Container. sql is the name of the new SQL Server container running on the new machine.

sudo docker exec -it sql mkdir /var/opt/mssql/backup

6. Run the following command on the new machine to copy the database from your new machine into the new SQL Server container. Replacing `Machine 1-2023818-11-11-11.bak` with the name of your backup file.

sudo docker cp Machine-1-2023818-11-11-11.bak sql:/var/opt/mssql/backup

7. Open Azure Data Studio, right click on your database name and select restore. The current version of Azure Data Studio requires the database to exist before you can do a restore. If the database does not already exist, create a blank database to restore to. Navigate to the location inside the container that was created in the previous step.

That should do it. You are now able to use the old database. I actually ran into an issue during this process where the containers I was using for my new M1 machine were too far ahead of the existing database that I was trying to transfer over. So keep in mind that depending on the container you are using, certain versions of SQL Server might not be compatible between Docker instances.

Armchair Expert Books

Armchair Expert Books

Or how to get the cover image of a book programmatically. The Armchair Expert Media Explorer is a website I built to track all the book, movies, documentaries, podcasts and shows that are discussed on the Armchair Expert podcast between the hosts, Dax and Monica, and their guests. For each book that is entered into the database, I make a call to an API to get the book cover image. This blog post will show how to get the cover image of a book programmatically using C# and a third party API.

Call the GetBook method

I’m using the OpenLibrary API to retrieve book covers for newly added books. The process to return a book cover image is two steps. Firstk call the search endpoint to search for a book by its title and author. The object returned will contain a variable called CoverI which is identifies the cover image for the book.

The second step in the process is to call the endpoint by passing the CoverI property returned from the first step in the url. You’re able to tell the API what size cover image you want (ie S, M, L, XL) by appending the size to the end of the CoverI property – like {book.CoverI}-M. In my case I’m asking for size medium.

Here you can see the starting point to get the book object. I first make a call to `GetBook` by passing in the title and the author as string variables.  

var book = await GetBook(title, author);    if (book != null && book?.CoverI != 0)    {        var thumbnailUrl = $"{book.CoverI}-M.jpg";    }

GetBook method

The GetBook method takes title and author to make a GET request to the search endpoint of the OpenLibrary API. I’m casting the response as OpenLibraryBookResponse which allows me to look at the returned list of documents. Note that Docs is a List<T> because the search returns books that match the search parameters. It’s highly likely you will return multiple results from your searches.

In this case, if the title and author search comes up empty, I make another call to just search by the title. This pattern works for 99.99% of all books I’m searching for.

 public async Task<OpenLibraryBook> GetBook(string title, string author)    {        using var httpClient = new HttpClient();        var url = $"{HttpUtility.UrlEncode(author)}&title={HttpUtility.UrlEncode(title)}";        var response = await httpClient.GetAsync(url);        var content = await response.Content.ReadAsStringAsync();        var bookResponse = JsonConvert.DeserializeObject<OpenLibraryBookResponse>(content);        if (bookResponse.Docs == null || bookResponse.Docs.Count() == 0)        {            Console.WriteLine(url);            url = $"{HttpUtility.UrlEncode(title)}&mode=everything";            response = await httpClient.GetAsync(url);            content = await response.Content.ReadAsStringAsync();            bookResponse = JsonConvert.DeserializeObject<OpenLibraryBookResponse>(content);            if (bookResponse.Docs == null || bookResponse.Docs.Count() == 0)            {                Console.WriteLine(url);            }        }        return bookResponse.Docs.FirstOrDefault();    }   

OpenLibraryBook class

This is the class representation of the OpenLibrary API response and book search results. Using JsonConvert.DesrializeObject will convert the JSON response into an object based on the classes below. Once the results has been deserialized into an object you can freely explore all of the properties.

using Newtonsoft.Json;public class OpenLibraryBookResponse{    public int NumFound { get; set; }    public int Start { get; set; }    public bool NumFoundExact { get; set; }    public OpenLibraryBook[] Docs { get; set; }}public class OpenLibraryBook{    [JsonProperty("key")]    public string Key { get; set; }    [JsonProperty("seed")]    public string[] Seed { get; set; }    [JsonProperty("type")]    public string Type { get; set; }    [JsonProperty("title")]    public string Title { get; set; }    [JsonProperty("title_suggest")]    public string TitleSuggest { get; set; }    [JsonProperty("title_sort")]    public string TitleSort { get; set; }    [JsonProperty("edition_count")]    public int EditionCount { get; set; }    [JsonProperty("edition_key")]    public string[] EditionKey { get; set; }    [JsonProperty("publish_date")]    public string[] PublishDate { get; set; }    [JsonProperty("publish_year")]    public int[] PublishYear { get; set; }    [JsonProperty("first_publish_year")]    public int FirstPublishYear { get; set; }    [JsonProperty("number_of_pages_median")]    public int NumberOfPagesMedian { get; set; }    [JsonProperty("lcc")]    public string[] Lcc { get; set; }    [JsonProperty("isbn")]    public string[] Isbn { get; set; }    [JsonProperty("last_modified_i")]    public long LastModified { get; set; }    [JsonProperty("ebook_count_i")]    public int EbookCount { get; set; }    [JsonProperty("ebook_access")]    public string EbookAccess { get; set; }    [JsonProperty("has_fulltext")]    public bool HasFulltext { get; set; }    [JsonProperty("public_scan_b")]    public bool PublicScan { get; set; }    [JsonProperty("ratings_count_1")]    public int RatingsCount1 { get; set; }    [JsonProperty("ratings_count_2")]    public int RatingsCount2 { get; set; }    [JsonProperty("ratings_count_3")]    public int RatingsCount3 { get; set; }    [JsonProperty("ratings_count_4")]    public int RatingsCount4 { get; set; }    [JsonProperty("ratings_count_5")]    public int RatingsCount5 { get; set; }    [JsonProperty("ratings_average")]    public double RatingsAverage { get; set; }    [JsonProperty("ratings_sortable")]    public double RatingsSortable { get; set; }    [JsonProperty("ratings_count")]    public int RatingsCount { get; set; }    [JsonProperty("readinglog_count")]    public int ReadinglogCount { get; set; }    [JsonProperty("want_to_read_count")]    public int WantToReadCount { get; set; }    [JsonProperty("currently_reading_count")]    public int CurrentlyReadingCount { get; set; }    [JsonProperty("already_read_count")]    public int AlreadyReadCount { get; set; }    [JsonProperty("cover_edition_key")]    public string CoverEditionKey { get; set; }    [JsonProperty("cover_i")]    public int CoverI { get; set; }    [JsonProperty("publisher")]    public string[] Publisher { get; set; }    [JsonProperty("language")]    public string[] Language { get; set; }    [JsonProperty("author_key")]    public string[] AuthorKey { get; set; }    [JsonProperty("author_name")]    public string[] AuthorName { get; set; }    [JsonProperty("author_alternative_name")]    public string[] AuthorAlternativeName { get; set; }    [JsonProperty("subject")]    public string[] Subject { get; set; }    [JsonProperty("publisher_facet")]    public string[] PublisherFacet { get; set; }    [JsonProperty("subject_facet")]    public string[] SubjectFacet { get; set; }    [JsonProperty("_version_")]    public long Version { get; set; }    [JsonProperty("LccSort")]    public string LccSort { get; set; }    [JsonProperty("author_facet")]    public string[] AuthorFacet { get; set; }    [JsonProperty("subject_key")]    public string[] SubjectKey { get; set; }}

And that is it. If you use the GetBook method along with the supporting classes you should be able to programmatically get cover images of books from the OpenLibrary API.

React Native: Loaded “env” configuration for the “production” profile: no environment variables specified.

React Native: Loaded “env” configuration for the “production” profile: no environment variables specified.

This is a post to describe a fix for an error in React Native, specifically an Expo Bare project, that looks like “Loaded “env” configuration for the “production” profile: no environment variables specified”.

I ran into this bug trying to upgrade a client’s React Native Android app. The app is built using Expo and I’m using eas to build and submit both iOS and Android apps to their app stores. After I made a code change, I started a build using eas build but after the build started I remembered I didn’t was logged into Expo with the wrong user account. So I killed the build process, logged out of my Expo account, opened a new terminal and logged into the Expo CLI with the right credentials.

I ran a new eas build but the build failed with the following error:

✔ Select platform › AndroidLoaded "env" configuration for the "production" profile: no environment variables specified. Learn more: don't have the required permissions to perform this operation.Entity not authorized: AccountEntity[ID] (viewer = RegularUserViewerContext[ID], action = READ, ruleIndex = -1)Request ID: ID    Error: GraphQL request failed.

I did some Googling and found this helpful Github post which made me realize my mistake. When I started the first build using my own Expo account instead of the client’s Expo account, the build process wrote a JSON property called extra.eas.projectId to the app.json file of my project. Because I didn’t have a project under my Expo account that matched what I was trying to build, Expo created a new project with a new id in my account using with the value written to extra.eas.projectId. As soon as I deleted the extra property from app.json I was able to build the project successfully again.

At first I didn’t think the error made any sense, but the more I thought about it you can see that “Entity not authorized” actually makes sense here – the first part about the env configuration is a little bit of a red herring though. So what happened was Expo was trying to build my project under an Expo project that the current user didn’t have access to. Which makes sense now.

So, in conclusion if you run into this error, delete this property from your app.json file and do a new eas build command. The new command will create a new extra.eas.projectId with the appropriate project id and you’ll be golden.

"extra": {      "eas": {        "projectId": "12345678-1234-1234-be5b-f844590a0aad"      }    }
Armchair Media Explorer: Running list of all media

Armchair Media Explorer: Running list of all media

We’ve added a running list of all the recommended and mentioned books, songs, articles, podcasts, movies, documentaries and shows from each episode of Armchair Expert podcast to the Armchair Expert Media Explorer.

List of movies mentioned in Armchair Expert podcast episodes

Every media entry is separated by the media type so users can easily toggle between the different types, like from books and documentaries, for example. You can also toggle to sort by the most mentioned media and alphabetical by name. The list is also displayed using movie posters and cover art from various sources. I really like displaying the media in this format, especially the movies and documentaries.

There are so many nights when just sit on the couch with no idea what to watch. Now I fire up the Armchair Expert Media Explorer and have a hand picked list of recommended movies. The movies are not only recommended or talked about by Dax and Monica, but these are all the movies that are talked about from each episode of the podcast. It’s very nice and easy browsing when you’re in need of something to watch, listen or read.

So if you are in the mood to find something new to watch, head over to the Armchair Expert Media Explorer and check things out.

How I completely destroyed a production database

How I completely destroyed a production database

This is a story about how I completely destroyed a production database, what I did to fix it and the lessons I learned that I carry with me today.

My first job out of “college” was working as a developer assigned to maintain a third party financial application that managed stock options of clients of the large financial institution where I worked. It was Windows Forms based desktop app that installed on each of the individual user’s desktop machine. The app connected to a shared SQL Server database via a trusted connection. This means users permissions were managed at the database level via their Windows credentials. But if you knew a valid SQL user and password you were able to change the connection string from a Trusted Connection to use a SQL Server user id and password.

I was running two versions of the software on my local machine in preparation for a software update sometime in the future. Both versions of the software were pointing to their own DEV database on a DEV server. Each installation utilizing a trusted connection and not using SQL authentication to connect to the database. Part of the verification of the new system was to see how the data was returned – was version 1 returning the same data as version 2 kind of thing.

For some reason, and to this day I have no idea why or how this happened, but I wanted to compare production data. Without realizing it – or more accurately , without thinking – I updated the connection string of the new version of the app and pointed it to the production database. If I were connecting with my Windows account I don’t think the next thing would have happened. However, I also made the fatal mistake of updating the connection string to use the SQL Server sa user and password.

A very detailed and highly technical diagram depicting the situation I found myself in:

Highly technical flow chart diagram

Little did I know that the desktop software had an automatic database upgrade migration process that was executed if the program detected the database was any version behind the desktop version currently connecting to the database. As soon as I fired up the desktop app, the app read the updated connection string (SA!) and connected to the production database as the system administrator giving me all the destructive power I could handle. The application saw that the database was a previous version and therefore needed an upgrade.

I remember a message popping up that read something like “Are you sure you really want to do this action? It’s definitely going to blow some stuff up”. I clicked OK because i wasn’t thinking or didn’t realize I was pointing to the production database. I had yet to really break something in my young career.

Someone once asked me what the difference between a senior developer and a junior developer are. My thinking is that a senior developer has broken enough things in their career that they know how to go about getting them working again. The junior just hasn’t had the opportunity yet.

After clicking OK I remember watching a modal describing what was happening – updating tables..., migrating database values... – and thinking “Oh, no! I’ve made a huge mistake”.

She-made-a-huge-mistake GIFs - Get the best GIF on GIPHY

I just authorized the new version of the software to update the existing production database to the new version. The new version of the software updated the table structure and stored procedures which resulted in a lot of data loss. Effectively making that database unusable to anyone running the old version – which at this point was everyone in the company.

Realizing what had happened I tried running the software version everyone else was running. It failed and I knew I was done. Since this was was early in the morning , nobody else was in the office so I just sat there alone thinking about what had happened. I’ll never forget that lump of shame that just sat in my stomach and chest. This was my first major f’up of my career. I wasn’t sure what was going to happen. Maybe I’d be fired and escorted out the door. My stomach was a pit of despair and my career was over just when it was getting started.

One of my favorite DJs is John Richards on KEXP who frequently reminds us that we’re not alone. Luckily, I was not alone in this little screw up either. Sometimes it does feel like we are alone, but if we look hard enough we will see that we are not alone. I was working with a great senior engineer who would go on to be one of my mentors. A person who taught me a lot about the working world as well about software development. As the emails and phone calls started to come in from users who are unable to use the application, I went over to this person and put it all out.

From start to finish I explained the situation. What I was doing, trying to do, what happened, etc. He just got up, said “OK, let’s go fix this.” and we were off to the data center. That was it. I really thought he was going to unload on me, but he didn’t. Apparently, and after 20 years I’ve learned this many times over, but everyone screws up and the majority of the time we can fix the problem. So, we simply walked over to the data center and talked to the DBAs, explained the situation and within 20 minutes they had restored the previous night’s database back up. Done.

All the data was restored, the tables put back to their original state and all was good again. Since there were a good amount of users who couldn’t access their data in the morning, we needed to tell our bosses and get things in the open and explain the root cause. I was really afraid of their reaction and had no idea how they were going to take the news. But it turns out they were pretty understanding of the whole thing. Some more than others, but the ones we were less than understanding didn’t make for good managers anyway.

I’ve been meaning to put this story into words for a while now. Partially just to tell the story but also as a reference for anyone else out there who has also completely destoyed something. You’re not alone.

What I Learned

This experience, as brutal as it was then, has really taught me a lot and helped me in my programming career. Here are some things that I’ve learned and carried with my through my career.

  1. I should never have had access to the SA password on a production database and therefore never had that kind of power. As a developer for one but definitely not a developer with only a year of experience. Now when I get frustrated by overly secure DBAs I can understand why and respect the authority. In all my projects now I make sure that we don’t have any write permissions against the production database unless it’s absolutely necessary. It makes life a little more difficult, but this policy makes sure there is no way we’re overwriting a production database either.
  2. Double check everything. Like really take the time to look both ways on a one way street. Just because somethign isn’t suppose to happen or you don’t think it could happen, doesn’t mean that the thing won’t. Trying to be patient and think through actions that you’re taking.
  3. Honesty and owning up to your mistakes. Part of being a consultant is taking the blame for things. Sometimes even when they are not directly your fault.

The main lesson here is that nobody should ever give a 20 year old with little experience the SA password to a production database and trust that things are going to be alright.