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.