6 months after my initial deadline – I have finally shipped my first version of the application to Production!
In other projects, I have a bad habit of shipping code directly to master/main branches or messing around in a “production” environment directly. This time around, I realized that I need to have some discipline and create a TEST environment first
The work was broken down into the following categories (in ascending, priority order):
- Enable client and server code to interact with non-local infrastructure
- Track and abstract environment variables for the entire codebase (including API keys, secrets, etc…)
- Abstract and script build commands for three environments (local, TEST, and PROD)
- Set up Firebase, Stripe, and AWS to support TEST and PROD environments.
It took me the better part of 2 – 3 months to get a TEST environment up and running smoothly. I’ll explain more on those growing pains at the end of this post.
Enabling Client and Server Code
The bulk of this work was to utilize environment variables so my application knows where it’s operating.
The scope of this was mostly around Firebase and Stripe. For development, I’ve been heavily utilizing Firebase’s authentication and Real Time Database emulators.
For Stripe, my code was modified so that Stripe can emit (and my application can receive) webhook events based on the Stripe payment API.
Tracking Environment Variables
I took some time to audit and create “.env” files so I could quickly reference environment variables for my deployed environments.
Up until now, both applications were loading environment variables from various locations, including start scripts and configuration files.
These environment variables contain everything from API keys, environment information, debug settings, redirect URLs (for Stripe), and build parameters (for the React client build), and various settings for Firebase.
Build Scripts
Once I had the environment variables buttoned up, it was quick work to author a handful of scripts to utilize the write configuration files to create my TEST and PROD assets. The remaining task for me here is to author some automation to upload the bundles to S3 and ElasticBeanstalk.
For the server, I create a ZIP file of the entire codebase (including an ElasticBeanstalk configuration). For the client, I run the CRA (Create React App) build command that creates a nice build folder I upload to S3.
Right now, I manually upload client and server artifacts between TEST and PROD which is a small time-sink, I’ll automate both compression and upload of the assets in the coming days.
Deployed Environments
With TEST and PROD builds operational – the next big step was to harden and clean up my TEST environment before cloning things to PROD.
In AWS this meant standing up a new environment in ElasticBeanstalk (EBS) and ensuring I have the correct IAM policies in place for the environments (I provision other AWS infrastructure when my application starts).
I ran into a small speed bump at this point – EBS in test had trouble obtaining my SSL certificate. After a few days, I realized EBS needs an SSL certificate to be available in the same region you are deploying too. Oops. I had provisioned an SSL certificate to US-EAST-1 instead of US-WEST-2. With that out of the way (and TEST working smoothly), I moved over to migrating Firebase.
Firebase was also trivial to provision a PROD environment for both authentication and the Real Time Database (RTDB). Since I was on the free-tier of service, I upgraded to their Blaze plan so I could have multiple databases and sets of infrastructure.
Stripe made this migration even more elegant, they provide a simple “COPY TO PROD” button on their dashboard – and suddenly all my important settings in TEST were moved over to my PROD environment. I can actually accept real payments!
Bringing it all Together
With a PROD environment operational (and the aforementioned environment configuration files), shipping to PROD is simple as calling the build scripts and uploading the code. AWS, Firebase, and Stripe work very well together with the abstractions and scripts in place.
While getting PROD up and running was “easy”, I learned a lot getting TEST up and running smoothly.
Infrastructure Details
The big head scratcher I had to tackle was getting my client code (hosted on S3) connecting to my API server (hosted on EBS), with SSL/TLS in the loop.
The problem I was facing, my client code is being served from https://www.serendipitybio.com but my server code is served from https://api.prod.serendipitybio.com.
A modern browser won’t allow you to make connections to a second domain if it differs from the domain you’ve currently loaded, unless you have specific CORS headers returned to you by the server. Additionally, with the way I’ve set up and deployed my application, I can’t easily route everything through a single domain.
AWS offers various solutions for this problem but none of them fit my particular requirements. Instead, of spending a ton of time on this, I opted to enable CORS on my server (with a specific list of origins) and host my client code using CloudFront (with SSL/TLS).
My API server(s) now sit behind an application load balancer that terminates SSL and proxies incoming requests to the servers. I also leverage CloudFront to enable fast and secure static asset hosting.
This set up enables me to have full TLS/SSL connections and splits the codebase between client and server (which is a huge velocity win for deployments). Another big win here was that I didn’t have to make a large code change to enable CORS or get this deployment model operational. It was literally 4 lines of code!
What made shipping to prod PROD easier? I took detailed notes while figuring this all out for the TEST environment! I created a checklist of items and gotchas I would need to work through during the PROD deployment – and the bulk of the work was to simply go through that checklist.
What’s Next
Now that PROD is up and running – I’ll be shifting gears to customer acquisition! More on that soon.
Thanks!
- Jonathan
P.S. A big thank you to that special person in my life. You’ve been up with me during those late nights and encouraging me to keep going. You know who you are.