As Director of Developer Experience, I get to play with a number of different systems. There’s a lot of existing web applications on the market, especially across all of the many languages we support (Shocking, right?). That means I’ve gotten to see some applications that play very well with modern cloud-based hosting as well as some that, well… let’s say are less optimized for it.
So what makes an application “cloud-friendly”? What makes it “cloud-native”? In a practical sense, how can developers build applications that will work with a modern app hosting system such as Platform.sh rather than against it? If you have a legacy application you want to make more cloud-friendly, what areas should you focus on?
Let’s look at some of the key ways to make your application modern-host-friendly (by which we mean Platform.sh, of course).
There are a number of older applications on the market that do not follow these guidelines, for various (valid!) reasons. In many cases there are workarounds (either simple or elaborate) to make them play nice. In others it’s better if the application itself is modernized. In either case, these are the key areas to target to bring those older applications into the modern cloud hosting era.
This sounds familiar…
If you’re already familiar with the 12 Factor App, many of these recommendations will be very familiar. If you’re not, those guidelines are worth a read as they apply to most modern web applications. Just don’t treat them as a holy writ, of course.
What I’d like to talk about here is to come down a level to implementation, and see what those recommendations mean in practice when the rubber meets the road. (Or the bits hit the network. Or something.)
Cloud best practices
1. Separate directories with Git-tracked files to secure deployment process
This is a biggie. Platform.sh builds all of your code from Git into a read-only file system. That makes it more secure, re-deployable (when doing a fast-forward merge to production), and forces good development practices (like “for the love of Pete, don’t edit code in production!”). All good stuff.
Having code writable by the web server might look like a good idea at first. (It means that something like WordPress could update its plugins.) It is not. It is a very bad idea. It means that securing your server is going to be next to impossible. That you have no idea what is running. That your application is going to drift and rot. Having Git as the only way to change the running code is good. It makes stuff repeatable, testable, debuggable. And secure.
But most applications still need to upload files somewhere. So at Platform.sh we provide configurable managed writable file mounts. Unlike traditional 12 factors apps you don't have to use something like S3. You can run your unmodified application. And it works.
(You can still use S3, of course, but then you miss out on the fast data cloning for each environment that we offer.)
All we ask of you is to declare in your config file the directories that should be writable. But remember, having a directory with some files that are in Git and some that need to be writeable is not going to work. Just split those to separate directories and don’t confuse them.
2. Choose between Git and database for web app configuration management
Modern web applications are highly configurable beasts. But is that configuration stored on disk or in the database? Should you be able to download it, or manage it through Git like code? Do you need to be able to have an audit log of configuration changes?
This is an either-or question. Choose a path; Stick to it. Know the repercussions. Because Platform.sh clones your production database when creating a development environment, it works out-of-the-box with both options: configuration can be managed in and deployed from Git, or it can be in the database, treated like user generated content. Managing configuration in the database probably makes you more productive. But you lose traceability and merging those changes to production can be a brittle process. Managing all configuration in Git gives you a full audit-log, but requires a deploy for every change.
One of the most robust solutions here is provided by Drupal 8, which has a powerful configuration system designed from the ground up to allow user configuration from the UI, but safe export of configuration to disk where it can be checked into Git and then safely reimported on deploy. Other approaches are certainly valid, though, as long as the question “Is configuration code or content?” is clearly answered.
3. Map environment variables for your cloud app
One of the 12 Factor App recommendations is to use environment variables (“env vars”) for all connection-related configuration. Think database credentials, search index credentials, API keys, and so on. That’s a good recommendation, and is exactly how Platform.sh works. However, leveraging that properly requires an extra, oft-forgotten step: Reading those variables.
Every application names its credentials differently. Every cloud host provides its environment variables differently. That means every application needs a place to have at least minimal glue code to read the host-provided environment variables and inject them into the application’s settings system.
A good cloud-friendly application has a clear place for that glue code to live, and that glue code by necessity needs to happen at runtime. That means generating out a file full of constants (either in application code or in a static file like YAML or INI) won’t work, because the necessary information isn’t available when writing files! Putting that file into a writeable file mount is also a non-starter, as that’s potential security hole.
When building your application, do yourself a favor: Make sure there’s a “put code to map environment variables into settings here” marker that is clear and upfront.
Some modern frameworks have native support for reading environment variables directly into their configuration. Not all do, however, and sometimes they're insufficiently robust. If your application framework does not support this, you should try to recreate the functionality with your own glue code.
4. Don’t expect installation to be interactive
Many systems still try to have the user enter connection credentials through a UI, especially during an installer. That won’t work if the credentials are provided through environment variables. For installers, always check to see if the needed credentials are already set. If they are, great, you don’t need to ask the user for them!
After installation, while it can be very nice for the user to let them provide their own credentials for local development it must be easy to override those values from the environment. However that’s done, environment-provided connection information must be able to override anything provided through the UI.
5. Use dynamic paths and domains to avoid site breaks
Many script-based applications (PHP, Python) need to know what the absolute path is to their code base on disk in order to include their source code. That’s all well-and-good, but just as with database credentials that information should be provided at runtime from the environment, not hard coded in a file.
The same holds true for the domain that the application is running on. Even in a “traditional” dev/stage/prod model, each environment is going to have a different domain name. On Platform.sh, every branch can be an environment with its own unique domain name. Hard-coding that domain name in a config file, or worse yet in the database, is going to break the site on any environment other than production.
Instead, if the domain is needed for just request-address verification (which is good), provide a way to inject the correct domain from the environment at runtime. For multi-site or multi-domain cases, make sure the logic is dynamic based on some common base that is pulled from the environment. Life will be a lot easier that way.
6. App disposability for faster boot times
A key part of a 12 Factor App, and of read-only production systems, is disposability. The best way to handle updates is to prepare a new build artifact, knock over the old instance, and start the new one in its place. The faster that happens the less downtime you have. (It can only be zero if you know there are no data migrations to run.)
Ironically, PHP does the best here among major languages. Its shared-nothing architecture means that every request has its own bootstrap overhead, but it also means there’s virtually no bootup overhead after a new deployment. Once PHP-FPM starts you’re good to go.
Compile time matters for developer experience, but won’t cause downtime. Boot up time, however, can result in downtime if it takes too long. If your application takes 5 minutes to boot then that’s 5 minutes in which you’re not serving requests. The more can be pushed off to lazy initialization the better. The first few requests may take a little longer to serve, but they will still be served.
7. Build applications safely with isolated compilation
Many applications have a compile step, or build step, or whatever term. That doesn’t mean just compiled languages like Java or Go, however. It also includes:
- Any app that downloads third party dependencies on the fly (Composer for PHP, pip for Python, Bundler for Ruby, etc.)
- Any app that has a preprocessor (TypeScript for JavaScript)
- Any app that needs to generate code (compiled templates or injection containers in PHP)
- Any application that uses SASS or LESS
- Many other use cases...
The possibilities here are legion, and that’s fine; Platform.sh, like any good PaaS, natively supports having a build/compile step to turn “what’s in Git” into “what serves requests”, repeatably and reliably.
However, that compile step naturally happens in an environment other than production; shutting down the production runtime in order to compile a new version of the application is a no-go for most use cases. That new version needs to be able to build itself without replacing production, which means without access to a database or other services. The build process can do almost anything it wants in terms of code generation or compilation or reorganization… as long as it can do it without database access. Relying on stateful information in a database (be it SQL, or Redis, or MongoDB, or anything else) for compilation destroys the safe repeatability of an isolated build environment. Even trying to connect to stateful services is unsafe if you want reliability and consistency in your build.
Take whatever compilation steps you want in your application, but make sure they can run in isolation, not while connected to your production database.
Try real-world app testing on Platform.sh
Not sure how well your application will play with modern cloud hosting? Give it a try. Nothing beats real-world testing, and you can try out your application on Platform.sh free for a month to see if it is cloud-friendly. We suspect you’ll like the experience so much you’ll decide to stay, and your code will be better for it, too.