Adding a Dynamic Maintenance Mode to Your Rails App30 Aug 2015
In an ideal world, you could update your application with zero downtime, and your users would be rewarded with snazzy new features without noticing how they got there.
Of course, this is not always the case. There are times when downtime is inevitable, and having a proper and scalable maintenance strategy to deal with downtime is necessary, especially when making changes to the database.
The following post describes a strategy we recently used. We had two requirements.
- Allow some users to access the application while it’s undergoing maintenance.
- Serve a custom, internationalized template.
There are several ways to implement a maintenance strategy, but they typically work by bypassing requests to the application server and serving a static HTML page. Heroku has a built-in maintenance feature that works in a similar way. But it meets none of our requirements because it bypasses the app entirely.
In our case, requests must hit the application so that it can determine whether to allow access and which language to serve to the user.
We implemented this easily using
ENV variables, a controller action, and Rails’ built in I18n support. Here’s how we did it.
Remembering the user’s location
For better usability, we should remember the user’s intended location before redirecting her away. This way, when the app exits maintenance, we can redirect users back to the page they originally wanted.
This is sometimes referred to as friendly forwarding. Friendly forwarding is a feature we can reuse in other places, so it’s a good idea to implement it in a concern1 and mix it into
store_location saves the request URL in the session, but only if it’s a GET request 2. The
redirect_back_or method takes a default route to use if no forwarding URL is present in the session, and an options hash. This way we can forward the same options that
redirect_to accepts, including flash messages.
To implement maintenance mode, we use a concern that we mix into
There’s not much that requires explanation here. When the mode is enabled, the
MaintenanceMode concern redirects to the maintenance page unless the current IP address is whitelisted.
All that remains is to add a controller that renders the maintenance page, a route, and some tests.
skip_before_action ensures that we don’t check for maintenance mode when we are viewing the maintenance page itself. This stops the application from going into an infinite loop.
render explicitly allows us to set the HTTP status code to 503, or
:service_unavailable. The default status code in Rails is 200, or
Redirecting the user back to their intended location
We can stop now, but we should make one more usability improvement. We should redirect away from the maintenance page and back to the app if the mode is disabled.
This is a small change, but it’s important–especially if your maintenance page has no navigation–because many users will refresh the page hoping the app will appear again. If we don’t redirect the user back, she could be stuck until frustrated enough to type the app’s root URL in the address bar. Plus, we made the effort to add friendly forwarding, and unless we make this change we will not have a chance to use it here.
maintenance_controller#show, we should redirect back to the application under two conditions:
- If maintenance mode is disabled.
- If the IP address is whitelisted.
We can use a
before_action to add this extra functionality.
To enter maintenance mode, we set an
ENV variable on Heroku.
From the app’s perspective, it doesn’t really matter what the value of
MAINTENANCE_MODE is (or its name, for that matter), so enabled serves for clarity. Our logic checks for the presence of the variable, not its value.
To allow access to an IP address, we set another
ENV variable. Its value should be a comma-delimited list of IP addresses for whom we want to enable access.
And finally, to exit maintenance mode, we unset the variables.
We want to test a number of conditions:
- The app redirects to the maintenance page when the mode is enabled.
- The app does not redirect to the maintenance page when the mode is disabled, or if the current IP is whitelisted.
- The app redirects away from the maintenance page if the mode is disabled, or if the current IP is whitelisted.
- The app redirects back to the user’s intended location once maintenance mode is disabled.
And there it is, a flexible and dynamic maintenance mode for your Rails application.
This feature is useful when you need to restrict access but keep your app running. Otherwise, you might want to implement a maintenance page at the DNS or web server level, or use Heroku’s built in solution.
For example, when redirecting to the login page after a user requests a protected resource. ↩
There actually is an HTTP specification for redirecting POST requests. But it appears that most frameworks don’t handle this requirement adequately, so we generally don’t want to redirect back if the user was posting a form. For more on this topic, see this article on Programmers. ↩