We've been quietly rolling out support for hooks during deployments. This may come as a surprise if you listen to our ramblings about deployment processes, as we've always pushed for self-contained, zero-adherence builds. Don't worry, we haven't changed our minds. Let's see why and how doing things outside of the build system can make sense.
Our stance is unchanged
At Clever Cloud, we're uncompromizing on automation. The first step to automate production is to have robust, reproducible builds. The best way to be robust and reproducible is to get rid of implicit dependencies: if you have more than two operations to run in sequence to build and run your project, then you have a problem. Not just for deployment, mind you, but for your day-to-day productivity: working on your application becomes complicated, and onboarding new developers takes hours (or worse).
How are Node.JS apps deployed on Clever Cloud? Simple: npm install && npm start
. What about scala applications? sbt stage
. No problem, no vendor lock-in. That's nice and clean. Unfortunately it's not the whole story.
The case of frontend build
It's got to be the most common question we have about our deployment system: "how can I integrate my frontend build in the deployment phase"?
For Node.JS, easy, just add your build phase to scripts.install
. For scala applications, you can configure sbt-web
. For PHP applications, you can hook in composer. Hooking the frontend build in the main build can range from super easy to dwarf-fortress-fun. Some build tools allow to run arbitrary code, some are more restrictive (but more predictable). For instance, building Haskell applications with stack
makes it quite hard to shell out and run npm install
.
Monolithic builds are not the only option
In a full-stack setup (eg Ruby on Rails or Play! framework), integrating the front-end build in the build system is a good idea, as it gives you better integration (eg. play run
also refreshes compiled assets). If you're going with a minimalist tech stack however, integrating everything makes less sense, as your needs for the development environment are not aligned with your needs for the production environment. Let's say you work on a scotty
web app, with some assets compilation. For development, no need to bake everything in the web lib, you can use Devloop and have automatic recompiling on reload, both for frontend and backend.
In that case, instead of shoehorning the frontend build in a stack
setup, using a hook as an escape hatch is the most time-effective way to do it; it also gives you more control on when build tasks happen. This allows you to leverage our build system more efficiently.
Available hooks and when to use them
A complete rundown of hooks is available in the documentation, but I'll give you a concrete example to give you a better understanding of what you can do.
Hook | Description |
---|---|
`pre build` | This is a good place to run `npm install`, as it happens before the deps are cached: that way you benefit from dependencies caching for both frontend and backend builds. |
`post build` | That’s where you want to run the actual build target. It happens before the built artifact is saved, this way the frontend build is bundled with the rest of the compiled files. |
`pre run` | That’s only for specific setup you want to run every time (even when deploying from cache). |
`run succeeded` and `run failed` | These are mostly for notification purposes, as they happen when the application has already started (or failed to start). They can’t abort the launch, so they’re just there for debugging or notification purposes. |
Please give us feedback
I've created the new hooks system to scratch an itch, so it may change a bit in the following months. Please reach out to the support team if you have questions or suggestions.