Ever run into a situation where you manually deployed your API to AWS via the management console (one with lots of enviroment variables too!), only to realise you forgot to change the region of deployment?
It may not be the exact scenario but forgetting to switch regions for deployments can be a common occurrence; and in my case, I had previously deployed to us-east-1
but now needed the deployment to be eu-west-2
. I tried to search for any console features that could allow me “copy” the service into the new region (or update the current region) but that did not seem to be available, so I was left with repeating the entire process. Using Infrastructure-as-code to automate provisioning of the AWS resources would have saved me from this situation, hence this blogpost.
In this post, I’ll be sharing the Terraform (and shell) scripts I wrote to automate the deployment of a Node.js (Express) API with environment variables, including a database deployed on AWS RDS with MySQL.
Prerequisites
A Node.js/Express API (e.g. one of my repos)
AWS account
Terraform installed on your local machine
Setup
This was my directory structure:
Note: As you will see below, I created shell scripts to run the necessary Terraform commands due to the number of environment variables I needed to add to the API service.
-
variables.tf
variable "api_name" {
type = string
default = # name you want to give to your api service
}
variable "github_repo" {
type = string
default = # your GitHub repo url
}
variable "apprunner_connection_arn" {
type = string
}
variable "vpc_connector_arn" {
type = string
}
variable "env_prod_db_username" {
type = string
}
variable "env_prod_db_password" {
type = string
}
variable "env_prod_fe_url" {
type = string
}
# other variables
-
provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.67.0"
}
}
}
provider "aws" {
region = "eu-west-2"
}
-
output.tf
output "api_service_url" {
value = aws_apprunner_service.express-api-service.service_url
}
output "api_service_arn" {
value = aws_apprunner_service.express-api-service.arn
}
output "api_service_status" {
value = aws_apprunner_service.express-api-service.status
}
main.tf
Prerequisites:
1) You need to have a connection to your repository provider (i.e GitHub or BitBucket) to enable access to that repo for App Runner; this can be done manually.
The ARN for this connection is what you need for var.apprunner_connection_arn
.
2) You also need to create a VPC connector; this can be done with Terraform in this script. However, I had an existing VPC connector for the region, so the provisioning is not included in my script.
You can use the official documentation to set it up.
resource "aws_apprunner_service" "express-api-service" {
service_name = var.api_name
source_configuration {
auto_deployments_enabled = true
authentication_configuration {
connection_arn = var.apprunner_connection_arn
}
code_repository {
repository_url = var.github_repo
source_code_version {
type = "BRANCH"
value = "master" # or whichever branch you are deploying
}
code_configuration {
configuration_source = "API"
code_configuration_values {
build_command = "npm install"
port = "8000" # or the port your api runs on
runtime = "NODEJS_16"
start_command = "npm run start"
runtime_environment_variables = {
PROD_DB_PORT = "3306"
NODE_ENV = "production"
PROD_DB_PASSWORD = var.env_prod_db_password
PROD_DB_USERNAME = var.env_prod_db_username
PROD_FRONTEND_URL = var.env_prod_fe_url
# your other env variables
}
}
}
}
}
network_configuration {
ingress_configuration {
is_publicly_accessible = true
}
egress_configuration {
egress_type = "VPC"
vpc_connector_arn = var.vpc_connector_arn
}
}
instance_configuration {
cpu = "2048" #2vCPU
memory = "4096" #4GB
}
health_check_configuration {
interval = 10
timeout = 5
}
tags = {
DEPLOYED = "api-via-terraform"
}
}
And that’s it! Run the necessary terraform
commands with the scripts provided below:
terraform init
sh plan.sh
forterraform plan
and review if necessarysh deploy.sh
forterraform apply
Note: Before running these scripts, ensure to have the
.env
file with all the necessary environment variables.
plan.sh
#!/bin/bash
export $(grep -v '^#' .env | xargs)
terraform plan \
-var="apprunner_connection_arn=$APPRUNNER_CONNECTION_ARN" \
-var="vpc_connector_arn=$VPC_CONNECTOR_ARN" \
-var="env_prod_db_username=$PROD_DB_USERNAME" \
-var="env_prod_db_password=$PROD_DB_PASSWORD" \
-var="env_prod_fe_url=$PROD_FRONTEND_URL" \
# any other environment variables your API needs
deploy.sh
#!/bin/bash
export $(grep -v '^#' .env | xargs)
terraform apply \
-var="apprunner_connection_arn=$APPRUNNER_CONNECTION_ARN" \
-var="vpc_connector_arn=$VPC_CONNECTOR_ARN" \
-var="env_prod_db_username=$PROD_DB_USERNAME" \
-var="env_prod_db_password=$PROD_DB_PASSWORD" \
-var="env_prod_fe_url=$PROD_FRONTEND_URL" \
# any other environment variables your API needs
After successful deployment, you should see a success message in the terminal with the following outputs:
api_service_url = “«value»“
api_service_arn = “«value»”
api_service_status = “«value»“
[Optional] Service Update
For updates to the service, you can use sh update.sh
:
#!/bin/bash
export $(grep -v '^#' ../.env | xargs)
API_SERVICE_ARN=$(terraform output -raw api_service_arn)
# Updates the App Runner service with the configuration in the input.json file
aws apprunner update-service --service-arn $API_SERVICE_ARN \
--cli-input-json file://input.json
echo "App Runner service has been updated"
However, the input.json
file needed for the script needs to contain the JSON version of the entire service configuration (which seems like a pain tbh).
Is there a way to use a script similar to the above to update the service with the configuration updates only?🤔
Possible Issues You Might Encounter
If you have existing app runner connections and VPC connectors, ensure they are in the new region you are deploying to; these resources need to be located in the same region (your terraform script will throw an error otherwise).
After successful deployment, I ran into a database connection timeout error. However, this was related to the error shared in my previous post and I resolved it by allowing inbound access to the database via the security group of the app runner service.
Thanks for reading, and I hope you find it useful!
P.S If you have any suggestions on updating the service without rewriting the entire setup configurations, please let me know in the comments.
Top comments (0)