How to run npm ci from Terraform
Install Node dependencies during deployment in an optimized way
NPM dependencies in Lambda
You can define Lambda code inline in an archive_file
data source, but that works only for simple functions without any dependencies. For anything more
serious, you'll need a package.json
and npm
to install third-party libraries.
But then, before you upload the code to AWS you need to run npm ci
. Terraform's archive_file
does little to help here: it's a dumb construct that
zips anything that happens to be in the source directory. Which, if you forgot to install the dependencies breaks the function.
How to make Terraform run npm ci
automatically?
Running external programs
Terraform supports the external data source that provides support for arbitrary commands. This allows complex builds to run as part of the deploy process.
But npm ci
is needed only occassionally, so if it's run every time you run terraform apply
or terraform plan
, it slows down all deployments
considerably.
Fortunately, both the node_modules
and the package-lock.json
have a modification timestamp. npm ci
is only needed if the time of the
node_modules
is earlier than the time of package-lock.json
. And this is exactly what make
supports.
Create a makefile that has a node_modules
target that depends on package-lock.json
:
# Makefile
node_modules: package-lock.json
npm ci
In the Terraform config, run make node_modules
:
# main.tf
data "external" "build" {
program = ["bash", "-c", <<EOT
(make node_modules) >&2 && echo "{\"dest\": \".\"}"
EOT
]
working_dir = "${path.module}/src"
}
data "archive_file" "lambda_zip" {
type = "zip"
output_path = "/tmp/lambda-${random_id.id.hex}.zip"
source_dir = "${data.external.build.working_dir}/${data.external.build.result.dest}"
}
resource "aws_lambda_function" "lambda" {
# ...
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
}
Note the result value of the external build ({dest: "."}
) and the source_dir
argument of the archive_file
. This defines an implicit dependency between
the two, so the dependencies are guaranteed to be installed when the archive_file
zips the folder.