TL;DR: Manage Vercel projects through code instead of the dashboard. You’ll set up Terraform, define your project and env vars as .tf files, and deploy with terraform apply. Includes patterns for dynamic environment variables and custom domains.
The problem
Managing Vercel project through the dashboard is fine for small projects, but as your infrastructure grows, it becomes cumbersome and can lead to errors like configuration drift or inconsistent environments. You might find yourself repeating the same steps for multiple projects, struggling to keep track of changes, or needing to collaborate with a team.
In this article I will show you how to leverage the IaC (Infrastructure as Code) approach using Terraform to manage your Vercel projects efficiently.
What is Infrastructure as Code (IaC)?
Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure through configuration files, rather than clicking through a user interface. There are several tools available for IaC, but Terraform is one of the most popular due to its simplicity and wide range of supported providers.
Vercel is fantastic for deploying web applications, but managing multiple projects, domains, and environment variables through the dashboard can become tedious, especially as the project grows. Terraform allows you to:
- Version control your infrastructure
- Replicate environments consistently
- Automate deployments across teams
- Track changes through pull requests and reviews
Why not just use Vercel CLI? While vercel CLI is great for deployments, Terraform excels at managing infrastructure state, tracking changes over time, and handling complex multi-environment setups. Think of Terraform as your infrastructure versioning system.
Prerequisites
Before we start, make sure you have:
- Installed Terraform
- A Vercel account
- A Github repository with your Next.js (or any other framework) project
Get Your Vercel API Token
First, generate an API token from your Vercel account:
- Go to Vercel Account Settings
- Click “Create”
- Give it a Token name, select a Scope and set Expiration
- Copy the token immediately (you won’t see it again)
Store this token securely - we’ll use it to authenticate Terraform.
Note: The token needs at least read and write permissions for your projects. If working with a team, ensure the token has team-level access.
Set Up Your Terraform Project
Clone, or initialize a new Next.js project if you don’t have one already. Then create a new directory for your Terraform configuration in your project root:
mkdir infra && cd infra
Create a main.tf file. This file will contain the main configuration for your Vercel resources:
touch main.tf
Now add a required providers, in our case it’s the Vercel provider, but you can add others as needed:
# file: infra/main.tf
terraform {
required_providers {
vercel = {
source = "vercel/vercel"
version = "~> 4.0.0"
}
}
}
provider "vercel" {
# Or omit this for the api_token to be read
# from the VERCEL_API_TOKEN environment variable
api_token = var.vercel_api_token
# Optional, team name for all resources
# Can be found in Vercel dashboard URL when switching teams
# https://vercel.com/account/settings
team = "{{your-team-id}}"
}
Now it’s time to create a variables.tf file where we will define our variables (not the values, just the types and descriptions):
touch variables.tf
Add the following content to variables.tf:
# file: infra/variables.tf
variable "vercel_api_token" {
type = string
description = "Vercel API Token"
sensitive = true
}
And finally, create a terraform.tfvars - a file to hold your variable values. This file should be added to your .gitignore to avoid committing sensitive information:
touch terraform.tfvars
Add your Vercel API token to terraform.tfvars:
vercel_api_token = "your-vercel-api-token-here"
After all these steps, your project structure should look like this:
your-nextjs-project/
├── infra/
│ ├── main.tf <- Where we define our Vercel resources
│ ├── variables.tf <- Where we declare our variables
│ └── terraform.tfvars <- Where we set our variable values (sensitive info)
├── src/
├── public/
├── package.json
└── ...
Configuring Vercel Project
Now it’s time to define your Vercel project in Terraform. To do this, we will use the vercel_project resource. Add the following to your main.tf:
# file: infra/main.tf
resource "vercel_project" "nextjs_with_terraform" {
# Vercel Project name (also used as subdomain name)
# Example: my-nextjs-app
name = "{{your-project-name}}"
# Framework preset, in our case nextjs
framework = "nextjs"
# Used node version, must be one of the supported versions by Vercel
node_version = "22.x"
# You can specify custom build and development commands
# build_command = "npm run build"
# Directory where the project is located
# Useful when the project is in a monorepo
# root_directory = "apps/web"
# Link to the Git repository
git_repository = {
type = "github"
# Example:
# repo = "JohnDoe/my-nextjs-app"
repo = "{{your-github-username}}/{{your-project-name}}"
}
}
Summary so far
Until now we have set up a Terraform project, configured the Vercel provider, and defined a Vercel project resource. Next, we will look into configuring environment variables, initializing Terraform, and applying the configuration to create the Vercel project.
Configuring Environment Variables
Having environment variables is crucial for most applications. You can manage them through Terraform as well. We will use the vercel_project_environment_variable resource in order to do this. Add the following resource to your main.tf:
# file: infra/main.tf
resource "vercel_project" "nextjs_with_terraform" {
# Collapsed for brevity ...
}
resource "vercel_project_environment_variable" "example_env_var" {
project_id = vercel_project.nextjs_with_terraform.id
key = "NEXT_PUBLIC_EXAMPLE_ENV_VAR"
value = "MyExampleValue"
target = ["production", "preview", "development"]
# To make it sensitive, uncomment the following line
# sensitive = true
# Optionally, provide the comment of the environment variable
# comment = "An example environment variable"
# If you want to specify a git branch, use the following argument
# Only if target is set to "preview"
# git_branch = "main"
}
We can also specify variables for a particular environment:
# file: infra/main.tf
resource "vercel_project" "nextjs_with_terraform" {
# Collapsed for brevity ...
}
# Separate environment variable for preview and production environments
resource "vercel_project_environment_variable" "stripe_secret_test" {
project_id = vercel_project.nextjs_with_terraform.id
key = "STRIPE_SECRET_KEY"
value = var.stripe_secret_test
target = ["preview"] # Only for preview
sensitive = true
comment = "A secret key for Stripe in preview environment used in checkout."
}
resource "vercel_project_environment_variable" "stripe_secret_live" {
project_id = vercel_project.nextjs_with_terraform.id
key = "STRIPE_SECRET_KEY"
value = var.stripe_secret_live
target = ["production"] # Only for production
sensitive = true
comment = "A secret key for Stripe in production environment used in checkout."
}
Adding variables this way seems repetitive, so we can use another resource called vercel_project_environment_variables which allows us to define multiple variables in one resource:
# file: infra/main.tf
resource "vercel_project" "nextjs_with_terraform" {
# Collapsed for brevity ...
}
resource "vercel_project_environment_variables" "sentry_variables" {
project_id = vercel_project.nextjs_with_terraform.id
variables = [
{
key = "SENTRY_DSN"
value = "https://[email protected]/XXXXYYYY"
target = ["preview", "production"]
},
{
key = "SENTRY_ENVIRONMENT"
value = "preview"
target = ["preview"]
},
{
key = "SENTRY_ENVIRONMENT"
value = "production"
target = ["production"]
},
]
}
Eventually, we can leverage the for_each construct to dynamically create environment variables from a map defined in variables.tf:
# file: infra/variables.tf
variable "env_vars" {
type = map(object({
value = string
target = list(string)
comment = optional(string)
sensitive = optional(bool, false)
}))
description = "Map of environment variables to set in Vercel project"
}
# file: infra/main.tf
resource "vercel_project_environment_variable" "dynamic_env_vars" {
for_each = var.env_vars
project_id = vercel_project.nextjs_with_terraform.id
key = each.key
value = each.value.value
target = each.value.target
comment = lookup(each.value, "comment", null)
sensitive = lookup(each.value, "sensitive", false)
}
# file: infra/terraform.tfvars
env_vars = {
"NEXT_PUBLIC_API_URL" = {
value = "https://api.example.com"
target = ["production", "preview", "development"]
},
"DATABASE_URL" = {
value = "postgres://user:password@host:port/dbname"
target = ["production"]
sensitive = true
comment = "Database connection string for production"
},
"ANALYTICS_KEY" = {
value = "analytics-key-value"
target = ["preview", "development"]
comment = "Analytics key for non-production environments"
}
}
Adding a Custom Domain
Want to add a custom domain? Update your main.tf to include the vercel_project_domain resource (keep in mind you need to own the domain and have it set up in Vercel dashboard first):
# file: infra/main.tf
resource "vercel_project_domain" "my_domain" {
project_id = vercel_project.nextjs_with_terraform.id
domain = "example.com"
}
Initializing and Applying Terraform Configuration
Once you have your configuration files ready, it’s time to initialize and apply the Terraform configuration to create your Vercel project.
Initialize Terraform. This step downloads the necessary provider plugins:
terraform init
Preview the changes that Terraform will make to your infrastructure:
terraform plan
Apply the configuration. This step will create or update your Vercel project based on the Terraform configuration.
terraform apply
Type yes when prompted, and Terraform will create your Vercel project along with any defined resources.
Cleaning Up
To remove the resources created by Terraform entirely from your Vercel account, run:
terraform destroy
Type yes when prompted, and Terraform will destroy your Vercel project!
Adding files to .gitignore
Make sure to add terraform.tfvars and .terraform/ directory to your .gitignore file to avoid committing sensitive information and local state files:
# Terraform .gitignore
# Local .terraform directories
.terraform/
# .tfstate files
*.tfstate
*.tfstate.*
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Optional: ignore graph output files generated by `terraform graph`
# *.dot
# Optional: ignore plan files saved before destroying Terraform configuration
# Uncomment the line below if you want to ignore planout files.
# planout
Best Practices
Use Remote State
Store your Terraform state remotely for team collaboration (requires additional providers and setup). Example using AWS S3 (Simple Storage Service):
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "vercel/terraform.tfstate"
region = "us-east-1"
}
}
Organize with Modules
As your infrastructure grows, organize resources into modules:
infra/
├── modules/
│ ├── vercel-project/
│ └── vercel-domain/
├── main.tf
└── variables.tf
Separate Environments
Use workspaces or separate directories for different environments:
terraform workspace new production
terraform workspace new staging
Common Issues and Solutions
Issue: “401 Unauthorized” error
Solution: Double-check your API token and ensure it has the correct permissions.
Issue: Project already exists
Solution: Import the existing project: terraform import vercel_project.my_project prj_xxxxx
Issue: Environment variables not updating
Solution: Vercel caches deployments. Trigger a new deployment after applying changes.
Conclusion
Terraform and Vercel work beautifully together for infrastructure automation. By managing your Vercel projects as code, you gain reproducibility, version control, and easier collaboration with your team.
Start small with a single project, then gradually expand your Terraform configuration as you become more comfortable with the workflow.
Repository
Code snippets from this article can be found in this GitHub repository
Additional Resources
Summary
In this guide, we’ve explored how to set up Terraform to manage Vercel projects, including defining projects, environment variables, and custom domains. Terraform’s declarative approach allows you to version control your infrastructure, automate deployments, and collaborate effectively with your team. There are multiple providers and resources available to extend this setup even further and combine it with other cloud services. In a large codebases, an infra typically lives in a separate repository spanning multiple services, but for simplicity, we kept it in the same repository as the application code.
Hope you found this guide helpful and encouraging you to explore Infrastructure as Code with Terraform and Vercel to save time and reduce errors in your deployments!