Build a Staging Site in 15 Minutes

Arlan Jaska
Affinity
Published in
3 min readJun 7, 2018

--

This is how we added a staging site to Affinity without touching a line of application code.

Background

At Affinity, we do a big deploy of our main branch once per day, and smaller deploys of specific features or bug fixes throughout the day. We’ve found that this provides a good balance of preventing bugs from popping up throughout the day while also keeping the code in production fresh.

One developer pain point we had was that after merging in code, it might not go live for hours (or days, if it was merged in on the weekend). We wanted a way to be able to test our changes in a production environment, before our changes reached our users.

After considering a few different approaches for building the staging environment, we decided that the fewer lines of code we touched, the more realistic our staging environment would be. Our key insight was that we could implement staging without touching the application code at all — by changing configuration at the nginx level.

How we did it

Normally, we use nginx to pass through requests to our application servers which run our deploy branch. These servers respond to requests for *.affinity.co, where the subdomain identifies the customer. Each of our customers gets their own subdomain. For example, affinity.affinity.co serves Affinity’s own instance of Affinity. Our goal was to create a new subdomain, staging.affinity.co, to serve the same data as affinity.affinity.co, but running a more recent version of our application code. That way, we’d be able to try things on our staging server with real data.

We started by spinning up an additional instance of our application server to serve our staging site. The only change we had to make here was to specify our main branch instead of our deploy branch. We added a cronjob to check for new builds every 5 minutes and then re-deploy the staging server.

Then, we added special nginx configuration listening for the domain staging.affinity.co. For requests directed to this staging domain, we rewrite the headers as if the request were going to the main site before passing it off to the staging server instead of the regular application server.

That means that the staging server has no idea it isn’t affinity.affinity.co!

To finish things up, we also have to rewrite some headers coming back from the staging server to fix redirects.

Implementation

This required just a few lines of nginx configuration. Depending on your production setup, this configuration could look a little different; but as long as you apply the right headers you’ll be fine.

# tell the server it is actually handling a affinity.* request
proxy_set_header Host affinity.affinity.co;
proxy_set_header Origin https://affinity.affinity.co;
# you may also want to set the Referer header
proxy_set_header Referer https://affinity.affinity.co$request_uri$is_args$args;

# re-write the Location header in responses
proxy_redirect https://affinity.affinity.co/ https://$host/;

If you’re using a Kubernetes nginx ingress controller, you can make a separate ingress controller configuration file and use the following annotations:

nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header X-Forwarded-Host affinity.affinity.co;
proxy_set_header Origin https://affinity.affinity.co;
proxy_set_header Referer https://affinity.affinity.co/$request_uri$is_args$args;
nginx.ingress.kubernetes.io/proxy-redirect-from: https://affinity.affinity.co/
nginx.ingress.kubernetes.io/proxy-redirect-to: https://$http_host/

Results and Taking It Further

This worked really well for testing out new code in a realistic production environment! After trying it out for a few weeks, we decided to always serve from staging for the instance of Affinity that we at Affinity use ourselves. Because we’re heavy users of our own platform, this has helped to catch bugs and regressions before they reach our customers.

--

--