A low-cost, n8n on AWS stack using modern AWS and Terraform best practices:
- Terraform 1.14.x + AWS provider 6.x for IaC.
- Single‑AZ VPC, Route 53 + Elastic IP, EC2 + Docker + Caddy.
- AWS Systems Manager:
- Parameter Store for per‑environment config/secrets.
- Session Manager for SSH‑free EC2 access.
- Patch Manager / State Manager for automated patching and drift control.
- CodePipeline + CodeBuild + CodeDeploy CI/CD wiring (skeleton).
It is designed as a template and a solid starting point for real deployments.
-
Environment‑aware Terraform layout
infra/terraform/modulesfor reusable components andinfra/terraform/envs/*for env stacks, matching AWS/HashiCorp guidance.
-
Networking and ingress
- Single‑AZ VPC and public subnet.
- Production ingress is
Route 53 -> Elastic IP -> Caddy -> n8n. - Public HTTPS stays on the custom production domain without exposing n8n on port
5678. - The active deployment path is one always-on production host; version validation happens locally before prod deploys.
-
Compute
- EC2 instance running Docker and
docker-composestack (n8n + Postgres). - Caddy runs on the host as the public reverse proxy and manages TLS directly on the instance.
- No SSH exposure. Administration is handled via AWS SSM Session Manager.
- EC2 instance running Docker and
-
Config / secrets (per env)
- AWS SSM Parameter Store hierarchy:
/n8n/<env>/app/.../n8n/<env>/db/postgres/.../n8n/<env>/network/allowed-ips-admin
- App is env‑agnostic; config is injected via environment variables at runtime, mirroring 12-Factor App methodology.
- AWS SSM Parameter Store hierarchy:
-
Management & patching
- SSM Patch Manager association for regular OS patching.
- SSM State Manager association to ensure Docker and the n8n stack stay in the desired state.
-
CI/CD (skeleton)
- CodeBuild project to build and push Docker images to ECR.
- CodeDeploy deployment group targeting the n8n EC2 instance.
- CodePipeline wiring Source → Build → Deploy stages.
n8n-aws-infra-template/
Makefile
README.md
.gitignore
docker-compose.yml
docker-compose.local.yml
docker-compose.aws.yml
Caddyfile
scripts/
fetch_ssm_env.sh # Pulls /n8n/<env>/... params into .env.<env>
deploy.sh # Used by CodeDeploy to restart docker stack on EC2
infra/
terraform/
modules/
vpc/
ec2_n8n/
route53/
ssm_params/
iam/
codebuild/
codedeploy/
codepipeline/
ssm_automation/
envs/
test/
# historical reference only
backend.tf
main.tf
variables.tf
outputs.tf
prod/
backend.tf
main.tf
variables.tf
outputs.tf
You extend the modules as your needs grow.
Add a login alias to your ~/.zshrc (or ~/.bashrc) so credentials and profile are set in one step:
awslogin() { export AWS_PROFILE=<your-profile>; aws sso login; }Then just run awslogin before any AWS work. All make and aws commands will pick up the profile automatically.
Tip
For multiple accounts, create one alias per profile:
awslogin-dev() { export AWS_PROFILE=my-dev; aws sso login; }
awslogin-prod() { export AWS_PROFILE=my-prod; aws sso login; }Or, without an alias:
aws sso login --profile <your-profile>
export AWS_PROFILE=<your-profile>After cloning this template, run make bootstrap env=prod once. It handles every one-time AWS setup step automatically:
awslogin
make bootstrap env=prodmake bootstrap will:
- Check that
awsCLI anddockerare available and your credentials work. - Create an S3 bucket + DynamoDB table for Terraform remote state (versioned, encrypted).
- Create an ECR repository for the n8n Docker image (scan-on-push enabled).
- Create all required SSM Parameter Store entries — auto-generates the n8n encryption key and Postgres password for you, prompts only for your domain.
- Enable the S3 backend block in
infra/terraform/envs/<env>/main.tf. - Write a complete
terraform.tfvarsfor the environment.
Important
One step cannot be automated via CLI: the GitHub CodeStar Connection. The script will pause and give you the direct console link. Takes ~60 seconds to complete in your browser.
After bootstrap, deploy production with:
make tf-init env=prod
make tf-plan env=prod
make tf-apply env=prod ARGS=-auto-approveLocal tools required to run the bootstrap script and Makefile:
-
AWS account with:
- S3 bucket + DynamoDB table for Terraform remote state and locking.
- Route 53 hosted zone for your domain.
-
Local tools:
- Terraform 1.14.x
- AWS CLI v2 (v2 required for SSO support)
- Docker + docker-compose
jq(for securely handling JSON configuration)
-
IAM permissions to manage VPC, EC2, Elastic IPs, Route 53, SSM, IAM, CodeBuild, CodeDeploy, CodePipeline.
Use AWS SSM Parameter Store per env.
For local, create parameters like:
/n8n/local/N8N_ENCRYPTION_KEY (SecureString)
/n8n/local/N8N_HOST (String) # localhost
/n8n/local/N8N_PROTOCOL (String) # http
/n8n/local/N8N_PORT (String) # 5678
/n8n/local/POSTGRES_USER (SecureString)
/n8n/local/POSTGRES_PASSWORD (SecureString)
/n8n/local/POSTGRES_DB (String)
Repeat the pattern for test and prod (with appropriate values — https, real domain, etc.).
The leaf segment of each path becomes the env var name injected into the container.
For production, also create:
/n8n/prod/TLS_CONTACT_EMAIL (String) # email used by Caddy for ACME registration
Local dev runs via Docker Compose, but loads config from SSM so it mirrors AWS.
Note: Terraform commands run inside Docker; credentials come from your configured AWS SSO profile via the ~/.aws mount.
- Log in (see AWS Authentication above):
awslogin- Create
/n8n/local/...SSM parameters:
make setup-localThis runs SSM setup and starts the stack in one step. Or run them separately:
make setup-ssm env=local # just SSM params
make up-local # just start the stackThis assumes .env.local already exists from make setup-local, then starts docker compose with the local overrides.
To validate an n8n upgrade locally before touching production:
make up-local-version N8N_UPSTREAM_REF=n8nio/n8n:<candidate-version>To tear down:
make down-localmake bootstrap env=prod handles the active setup automatically. To deploy manually or understand what's happening under the hood:
# 1. Bootstrap once (creates state bucket, ECR, SSM params, writes tfvars)
make bootstrap env=prod
# 2. Initialize the Terraform workspace
make tf-init env=prod
# 3. Review the plan
make tf-plan env=prod
# 4. Apply
make tf-apply env=prod ARGS=-auto-approveTerraform will create networking, the Elastic IP-backed production DNS record, EC2, IAM, SSM, and the full CI/CD pipeline.
The test Terraform environment remains in the repo only as historical reference. It is not part of the active documented deployment path.
To update SSM parameters at any time without re-running the full bootstrap:
make setup-ssm env=prodNo SSH port is exposed. Use AWS Systems Manager Session Manager instead.
CLI example:
aws ssm start-session --target <instance-id>The template includes modules and scripts for:
- CodeBuild: Build and push Docker images to ECR (you add the buildspec).
- CodeDeploy: Deployment group for EC2 instance (tagged
role=n8n), runningscripts/deploy.shto restart Docker Compose. - CodePipeline: Source → Build → Deploy stages wired together (you configure repo, branches, and ECR).
You provide:
- GitHub/CodeCommit repo details.
- ECR repo name.
- Your
buildspec.ymland CodeDeployappspec.ymlif you want fully automated deployments.
This template is intentionally opinionated to show strong defaults:
- No plaintext secrets in repo: use SSM SecureString.
- Environment‑specific paths:
/n8n/<env>/...per environment. - Least privilege IAM:
- EC2 role can read only its env’s parameters.
- SSM/logs permissions are limited to what Session Manager and CloudWatch need.
- No inbound SSH: Session Manager only.
- Automated patching: SSM Patch Manager keeps OS patched.
- Modern Terraform: 1.14.x and AWS provider 6.x pinned.