Serverless package done right
- aws parameter-store serverless bash
Problem
The Severless Framework package command claims to be useful in CI/CD workflows. The command results in the production of AWS Cloud Formation stack json files (or alternative cloud provider equivalent) on disk. These files can be bundled and considered the ‘deployment artifact’ at the end of the pipeline. The artifact can be provided to the serverless deploy
command that could be run at a later date.
This two-step package/deploy process is very familiar, so I was lured into using the serverless package
command. However, there is a major flaw in the design!
The behaviour of the command is to ‘bake in’ the value of the expected environment variables or AWS Parameter Store keys. This means your artifacts are not environment-agnostic. For example, if you include the stage parameter in the name of any functions or resources then the value of the stage parameter will be hardcoded in the artifacts. You therefore cannot promote these artifacts to any stage other than the one named when serverless package
was called.
Environment Agnostic
Build artifacts are supposed to be environment-agnostic. You should be able to deploy your artifacts to any environment and ‘promote’ through to production if desired. Artifacts must therefore be parameterised such that the target environment can provide everything the application needs. This is done using environment variables. The only ‘static’ value in the artifact should be the version number, provided at build time.
One option to side-step this problem is to perform the package step once per potential target environment. A deployment would require the versioned artifacts for the target environment. This option is cumbersome during the build phase and complicates the deploy phase. However, the main problem is the potential for the baked-in environment variables to become stale.
Let’s say we change the value of an environment variable after packaging for the production environment. This would require all the existing production-targeted artifacts to be rebuilt so as to bake-in the latest value. Only then are the new packages fit to be deployed. This creates CI overhead and requires consideration of how far back through artifact version history is sensible to rebuild.
Just-in-time Packaging
The only realistic option is to postpone the package step, i.e. don’t call serverless package
explicitly. The package command is implicitly called within the deploy command (as long as the --package
parameter is not provided). This package command deference is what happens when you call serverless deploy
against your serverless.yml. The environment variables are baked-in ‘just-in-time’ so the artifacts are fit for the target environment.
We need to bring just-in-time packaging to CI to produce environment-agnostic build artifacts. The solution is to neglect the serverless package
command and include the serverless.yml file into the build artifact. The deployment tooling must then execute the serverless deploy
command against the serverless.yml from within the artifact sometime later.
To make the bundle completely self-contained the package.json
file is also required alongside the serverless.yml. You could include your node_modules folder, or simply your package-lock.json
or yarn.lock
file and include a call to restore packages before serverless deploy
is called, as shown below:
Build artifact contents
$ tree ./deploy
.
├── package.json
├── package.zip # code bundle
├── serverless.yml
└── yarn.lock
Serverless.yml package snippet
package:
artifact: package.zip
Deployment steps
$ cd ./deploy
$ yarn install
$ yarn run serverless deploy --stage prod