Before you begin
What you need in place
This deployment builds a NetLiNQ EMS environment in your own AWS account with Terraform and Terragrunt: a VPC, an EKS cluster, managed databases and the NetLiNQ platform on top. BLiNQ provides the modules; you provide the account and the external services.
The Terraform modules
A private module bundle (blinq-ems-tf.zip) and, for ECR-based deployments, the Jenkins pipeline that pushes application images into your registry. Ask BLiNQ support for the release that matches your order.
The AWS account and services
An AWS account with permissions to create and manage every resource in the modules, a MongoDB Atlas project, a Route 53 hosted zone, and an SMTP server for account email.
Plan these before you run anything
MongoDB AtlasAn account, an organization ID and a project. The project must already exist — the modules create the cluster inside it, not the project itself.PostgreSQL (RDS)Created by the modules. Decide the database name Keycloak will use; keycloak is the usual answer.SMTP serverHost, credentials and client ID/secret from your mail provider. NetLiNQ sends account setup and maintenance mail through it.Region, AZs, sizingRegion and availability zones, EKS node instance types and counts, and the VPC CIDR plan. Changing these later means rebuilding infrastructure.Route 53 hosted zoneCreate the hosted zone for your domain yourself and keep its zone ID — the modules write DNS records into it.Decide first
Pulling images from your own registry
NetLiNQ's container images can come from BLiNQ's registry or from an ECR registry in your own AWS account. Decide this before you deploy — it changes what you set up and what you run.
Your choice
The cluster can reach public registries, so BLiNQ's registry works out of the box. Many customers still prefer their own ECR, for control over what runs in their account.
The only option
There is no NAT gateway and no route to the internet, so images must come from your own registry. See Private deployments.
Choosing your own registry means three things, in this order:
jenkins-ecr, so BLiNQ's pipeline can push the images into your registry. Covered below.generate_ecr_values.sh so every chart pulls from your registry rather than BLiNQ's.privateRegistryUri, so the Terraform modules agree with the values files.Rewriting the values files
generate_ecr_values.sh lives in the root of the configuration repository. Run it before the NetLiNQ apply:
# usage: ./generate_ecr_values.sh ecrRegistryId [awsRegion] [repoPrefix] [publicAwsEcrUri] $ ./generate_ecr_values.sh 123456789012 "us-east-1" $ ./generate_ecr_values.sh 123456789012 "us-east-1" "acmecorp/blinq"
ecrRegistryIdRequired. Your AWS account ID — the registry ID in front of .dkr.ecr.awsRegionDefaults to us-east-1.repoPrefixPath prefix inside ECR, default blinq/netlinq. It must match the ECR_REPO_PREFIX used when the images were imported. Pass "" for images sitting at the registry root.publicAwsEcrUriOptional. Left out, it is assumed to be the same as the private registry URI.It needs envsubst on the PATH, reads its templates from ecr_values_templates/, and writes the generated files back over the real values files — aws_values_files/, keycloak/oci/, monitoring/, secrets/ and the repository root. extract-aws-images.sh, in the same directory, lists the images that need mirroring in the first place.
Getting the images into your registry
Rewriting the values files only changes where the cluster looks. The images still have to arrive there. BLiNQ sends an import package for this — the ecr-to-external-ecr-transfer tooling — which pulls the release images from BLiNQ's registry and pushes them into yours.
vars/customer.vars.sh, set CUSTOMER_ECR_REGION and, for a local machine, AWS_PROFILE. The BLiNQ-side values arrive pre-filled — leave them alone../setup-iam.sh once. It creates BlinqImageImportRole in your account and writes customer-setup-output.sh. Send that file to your BLiNQ contact and wait for confirmation before going further../import-images.sh --dry-run first, then without the flag. It creates any missing ECR repositories, retries a failed image up to three times, and writes the result to images/imported-images.txt.$ chmod +x setup-iam.sh import-images.sh $ ./setup-iam.sh # once; send customer-setup-output.sh to BLiNQ $ ./import-images.sh --dry-run # preview, changes nothing $ ./import-images.sh
It needs Docker and AWS CLI v2, and a profile with access to your own account only. Prefer to create the role by hand? It needs sts:AssumeRole on the role ARN BLiNQ provides, ecr:GetAuthorizationToken, and push rights on your own registry:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeBlinqReadRole",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "<role ARN provided by BLiNQ>"
},
{
"Sid": "ECRLogin",
"Effect": "Allow",
"Action": ["ecr:GetAuthorizationToken"],
"Resource": "*"
},
{
"Sid": "PushToYourECR",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:DescribeRepositories",
"ecr:CreateRepository"
],
"Resource": "arn:aws:ecr:YOUR_REGION:YOUR_ACCOUNT_ID:repository/*"
}
]
}
ECR_REPO_PREFIX in vars/customer.vars.sh decides the namespace the images land under; it is the value you then pass to generate_ecr_values.sh as repoPrefix. Set one, use it in both places.When the import fails
not authorized to perform: sts:AssumeRoleAssumeBlinqReadRole permission.not authorized to perform: ecr:InitiateLayerUploadExpiredTokenException, or no basic auth credentials on pushTelling Terraform the same thing
privateRegistryUri = "<account-id>.dkr.ecr.us-east-1.amazonaws.com/blinq/netlinq"
repoPrefix you pass the script, the ECR_REPO_PREFIX the images were imported under, and the path on the end of privateRegistryUri are the same string. A mismatch shows up as image pull failures after a successful apply.Staying on BLiNQ's registry instead? Skip this section and the IAM user below, and leave privateRegistryUri empty.
Prepare
Install the tooling
Install these on the machine you will deploy from, at the versions this release was tested with:
Terragruntv0.76.3Terraformv1.14.8AWS CLIaws-cli/2.17.52kubectl1.35, matching the EKS version the modules create$ terragrunt --version $ terraform --version $ aws --version $ kubectl version --client
Prepare the host
Terragrunt deployments expect IPv6 disabled and forwarding on. Check what the host has now:
$ sudo sysctl -p
No output means the settings are not there yet. Add them, then run sudo sysctl -p again — it should echo the four lines back:
$ sudo tee -a /etc/sysctl.conf > /dev/null <<'EOF' net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1 net.ipv4.ip_forward = 1 EOF $ sudo sysctl -p
$ sudo apt update && sudo apt upgrade -y $ aws configure # sign in to the account you are deploying into
Prepare · ECR deployments only
Create the image-transfer IAM user
This step applies when you host NetLiNQ images in your own ECR. It lets BLiNQ's Jenkins pipeline push application images into your registry, so containers run from your account rather than being pulled from ours at deploy time.
Create an IAM user named jenkins-ecr with no console access. Generate an access key ID and secret access key, and hand them to BLiNQ for the pipeline. Attach the three inline policies below.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRAuthToken",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "ECRDockerPullAndPush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "arn:aws:ecr:*:*:repository/blinq/netlinq/*"
}
]
}
Authentication is account-wide; pull and push are confined to the blinq/netlinq/* namespace.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRCreateRepositoryOnPush",
"Effect": "Allow",
"Action": "ecr:CreateRepository",
"Resource": "arn:aws:ecr:*:*:repository/blinq/netlinq/*"
}
]
}
Lets the pipeline create a repository the first time it pushes an image that has none.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRPublicRegistryAuth",
"Effect": "Allow",
"Action": [
"ecr-public:GetAuthorizationToken",
"sts:GetServiceBearerToken"
],
"Resource": "*"
}
]
}
Lets the pipeline pull base images from AWS public registries.
Prepare
Unpack the modules and fix the paths
BLiNQ sends the Terraform modules as a private ZIP. Extract it, then point every Terragrunt file at where you put it.
The deployment tree ships two templates to copy from. They differ only in their inputs, not in their structure:
deployment/exampleThe public-facing deployment: AWS Transfer Family SFTP (sftp_server_type = "aws-tf"), external-dns on, a Route 53 zone ID filled in.deployment/example-privateThe private deployment: enable_sftp = false with sftp_server_type = "none", external-dns off, Kafka external access on.$ unzip blinq-ems-tf.zip -d /path/to/modules $ cd /path/to/modules $ pwd # this is the path you paste below
//: everything before it is the repository root, everything after is the module inside it. Write …/modules//awstransfer, not …/modules/awstransfer./home/blinq/terragrunt/blinq-ems-autom-configs/terragrunt/tf-mods/7.0.2_3/modules//awstransfer
Search the deployment tree for <your-path-here> and replace every occurrence with your pwd output. These are the files and lines that carry it:
example/base/main.tfFour module sources: s3, mongo, rds and acm (lines 107, 236, 287 and 314 in this release)example/netlinq/main.tfnetlinq_blue, line 12example/netlinq_green/main.tfnetlinq_green, line 13example/secrets/main.tfsecrets-manager, line 15example/sftp/main.tfawstransfer, line 36Line numbers shift between releases — searching for the placeholder is the reliable way to find them all. example-private carries the same five files at slightly different lines.
example-private/secrets/main.tf the source reads <your-path-here>/modules/secrets-manager. Add the second slash when you edit it, or Terragrunt treats the whole path as the repository root and finds no module.Step 1
Deploy the secrets
Secrets go in first, because everything deployed afterwards reads them from AWS Secrets Manager.
$ terragrunt run-all plan --queue-include-dir=secrets --source-update $ terragrunt run-all apply --queue-include-dir=secrets --source-update
The apply creates the secrets empty. Fill in every value in Secrets Manager before you go further.
external_mongo_admin_secret appears as netlinq_external_mongo_admin_secret_lnt — prefix netlinq_, suffix _lnt. Search on the middle part.MongoDB Atlas
external_mongo_admin_secretAtlas admin, used for the initial setup. Keys: mongo-username, mongo-password.external_mongo_emsdb_secretRead/write on the netlinq database.external_mongo_spcdb_secretRead/write on the spc database.external_mongo_spckpidb_secretRead/write on the SPC KPI database.Keycloak and RDS
external_keycloak_admin_clientKeycloak admin console account, used to create the TOC and Diesel clients. Keys: keycloak-username, keycloak-password.external_rds_keycloak_clientThe PostgreSQL account Keycloak uses for its own configuration. Keys: pg-username, pg-password.SMTP
external_smtp_default_user_clientAccount setup and maintenance email. Keys: smtp-username, smtp-password, smtp-client-id, smtp-client-secret — the last two come from your mail provider.EMS administrator
external_ems_aoa_clientThe admin-of-admins account you log in with first. Keys: ems-username, ems-password.Rules for these values
A–Z and a–z.A-Z a-z 0-9 !@#$%^&*()_+-={}[]|;:'",.<>/?~. Sixteen characters or more is recommended./, @, " or a space.inputs.hcl; the password is generated. Its secret is named <prefix>-SFTP/username-<suffix>.Step 2
Fill in inputs.hcl
One file describes the whole deployment. Copy deployment/example/inputs.hcl (or example-private) and edit it — that file, not this page, is the complete template.
inputs.hcl runs to roughly 350 lines and also carries the RDS instance settings, the Keycloak node group and autoscaling block, MongoDB Atlas sizing and PrivateLink options, a long list of enable_* feature flags, the blue/green upgrade inputs, and the networking block. Work from the file and change what applies to you; leave the rest at its shipped default.General
inputs = { state_bucket_name = "prod-terragrunt-state-ems-1e" # S3 bucket holding Terraform state state_dynamodb_table = "terragrunt-lock-table-1e" # DynamoDB table for state locking enable_sftp = true enable_netlinq_green = false # greenfield upgrades only resource_prefix = "netlinq" # prefix on every AWS resource name aws_region = "us-east-1" netlinq_name = "blue-t3" # label used in names and tags endpoint = "example.blinqnetworks.org" emsui_subdomain = "emsui" # emsui.example.blinqnetworks.org sftp_subdomain = "sftp" mongo_db_name = "tocdb" rds_db_name = "keycloak" privateRegistryUri = "" # your ECR URI, for ECR deployments }
state_bucket_nameS3 bucket holding Terraform state, so it survives and can be shared across environments.state_dynamodb_tableDynamoDB table for state locking, which stops two applies from running at once.enable_sftp / sftp_server_typeWhich SFTP service gets deployed. The repo uses three combinations: true + "aws-tf" for AWS Transfer Family, true + "oem" for a local OEM server, and false + "none" for no SFTP at all.enable_netlinq_greenLeave false. Turn it on only for greenfield deployments during an upgrade.resource_prefixPrefix applied to AWS resource names, so one account can carry several deployments.endpointThe EMS endpoint URL, for example example.blinqnetworks.org.emsui_subdomainSubdomain for the EMS UI. emsui gives emsui.example.blinqnetworks.org.sftp_subdomainSubdomain for SFTP access.mongo_db_nameMongoDB database NetLiNQ EMS uses, for example tocdb.rds_db_namePostgreSQL database Keycloak uses, for example keycloak.privateRegistryUriPrivate ECR URI for image pulls, for example <account-id>.dkr.ecr.us-east-1.amazonaws.com/blinq/netlinq.Cluster
EMS_SERIALNUMBER = "EM-01234564879" SPC_SERIALNUMBER = "SP0-0000000000" aws_region = "us-east-1" access_key = "<loki-s3-access-key-id>" # Loki's S3 user secret_access_key = "<secret>" netlinq_name = "blue-t6" endpoint = "ems.example.org" emsui_subdomain = "emsui" sftp_subdomain = "sftp" mongo_db_name = "tocdb" rds_db_name = "keycloak" k8s_version = "1.35" # EKS version eks_nodegroup_instance_types = ["m6a.xlarge"] eks_nodegroup_capacity_type = "ON_DEMAND" # or SPOT eks_nodegroup_max_size = 9 eks_nodegroup_desired_size = 7 eks_nodegroup_min_size = 5 eks_nodegroup_max_unavailable_percentage = 30 eks_nodegroup_volume_size = 30 spc_chart_version = "2.6.1" netlinq_chart_version = "7.0.1" ekf_stack_version = "0.17.7" netlinq_stack_version = "2.25.29" keycloak_stack_version = "1.8.10" upgrade_stack_chart_version = "0.3.5" zone_id = "<route53-hosted-zone-id>" spc_tls_dir = "PATH_TO_TLS_DIR" # unzipped SPC TLS certs
deployment/example/inputs.hcl carries today — always use the versions BLiNQ ships with your package rather than copying them from any document. zone_id is the Route 53 hosted zone you created yourself; the modules write DNS records into it.The access_key and secret_access_key belong to a user created only so Loki and Promtail can write logs to S3. Give it nothing more than this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::prefix*",
"arn:aws:s3:::prefix*/*"
]
}
]
}
MongoDB Atlas
public_key = "" # Atlas API public key private_key = "" # Atlas API private key atlas_project_name = "prod-ems-mongo" # must already exist cluster_name = "" atlas_region = "US_EAST_1" aws_account_id = "" # AWS account linked to Atlas atlasorgid = "" atlas_vpc_cidr = "10.105.0.0/21" # Atlas-side network mongo_db_major_version = "8.0" cluster_type = "REPLICASET"
VPC and SFTP
sftp_bucket_name = "ems-sftp" sftp_name = "ems-root-sftp" blinqroot_username = "sftp-admin" subnet_cidr = "10.12.5.0/28" aws_mode = true # always true az = "us-east-1a" s3_name = "ems-internal" vpc_name = "ems-vpc" vpc_cidr = "10.12.0.0/20" aws-zone-count = 3 private_subnets = ["10.12.1.0/24", "10.12.2.0/24", "10.12.3.0/24"] public_subnets = ["10.12.10.0/28", "10.12.11.0/28", "10.12.12.0/28"] # only if spare space remains in the VPC CIDR enabled_additional_private_subnet = false additional_private_subnet_cidr = []
The shipped example uses a different plan from the one below: vpc_cidr = "10.121.0.0/20", private subnets as /23 and public as /27, with a second set of green subnets reserved inside the same CIDR for blue/green upgrades. Either plan works; what matters is that nothing overlaps. The sizing guidance from the runbook:
10.12.0.0/2010.12.1.0/24 (us-east-1a), 10.12.2.0/24 (1b), 10.12.3.0/24 (1c)/24 blocks if you want room to grow, /28 if limiting public IPs matters more10.12.13.0/28blinqroot_username is the SFTP root account; its password is generated for you. aws_mode stays true — it is what makes NetLiNQ configure itself for AWS. Set enabled_additional_private_subnet only when you need more subnet capacity and the VPC CIDR still has unused space; the extra CIDRs must not overlap anything existing.
Steps 3 and 4
Deploy
Two applies, in this order. Plan first each time and read what it intends to do.
Base and SFTP
$ terragrunt run-all plan --queue-include-dir=base --queue-include-dir=sftp \ --source-update $ terragrunt run-all apply --queue-include-dir=base --queue-include-dir=sftp \ --source-update
NetLiNQ
This builds the EKS infrastructure and deploys NetLiNQ EMS into the cluster, leaving the modules you already applied alone.
$ terragrunt run-all plan --queue-exclude-dir=sftp --queue-exclude-dir=base \ --queue-exclude-dir=secrets --source-update $ terragrunt run-all apply --queue-exclude-dir=sftp --queue-exclude-dir=base \ --queue-exclude-dir=secrets --source-update
After the deploy
Check that it worked
kubectl get pods -n netlinq — everything should settle into Running.<emsui_subdomain>.<endpoint> with the external_ems_aoa_client account.If something is wrong, pod logs are the first place to look. Contact EMS support if they do not explain it.
Variant
Private deployments
The deployment tree ships a second template, deployment/example-private, for running NetLiNQ on EKS inside a private VPC. One input decides the shape of the whole environment:
deployment_mode = "private-private" # or "private-public"
private-publicThe default. Private subnets for the cluster, public subnets for the load balancers, so management interfaces are reachable from outside.private-privateNothing is publicly reachable. Public subnets are ignored, and the deployment has no route to the internet at all.base/main.tf both enable_nat_gateway and single_nat_gateway are set from deployment_mode == "private-public", and the IGW is passed as empty otherwise. Everything the cluster needs — images, AWS APIs, Atlas — must therefore arrive over VPC endpoints or PrivateLink. Plan that before you apply, not after.Images must come from your own registry
A private-private cluster has no route to a public registry, so the registry step is not optional here. Follow Pulling images from your own registry above, then come back: generate_ecr_values.sh must have been run and privateRegistryUri set before the NetLiNQ apply.
VPC endpoints
enable_vpc_endpoints = trueRecommended. The base module creates the endpoints the cluster needs, with their own security group accepting HTTPS from the VPC.enable_vpc_endpoints = falseAssumes your VPC already has them. You are responsible for every service in the list below.What true creates, from base/main.tf: one Gateway endpoint for s3, attached to the private route tables, and Interface endpoints with private DNS for:
autoscaling ecr.api ecr.dkr ec2 ec2messages elasticloadbalancing
sts kms secretsmanager sqs ssm eks
DNS records are manual here
A private deployment runs with enable_external_dns = false, so nothing writes DNS records for you. Create the records for the EMS UI and the other endpoints by hand after the apply, pointing at the load balancers the deployment creates.
Certificates
Private deployments still use ACM certificates — this does not change with the deployment mode.
enable_acm = trueCreates a new ACM certificate.enable_acm = falseUses one you already have — pass its ARN in existing_acm_certificate_arn. It must be in the deployment region and valid for the domains in use.MongoDB Atlas
connectivity_type"privateLink" keeps the connection on the AWS network and is the preferred option. "peering" sets up VPC peering, which traverses the internet.enable_mongo_vpc_endpointtrue creates the endpoint for PrivateLink. Set it false only when supplying an existing one in mongo_vpc_endpoint_id.create_network_containerAtlas needs a network container for both PrivateLink and peering. true for a new Atlas project; false only if you have verified one already exists.create_privatelinkStarts the PrivateLink connection.enable_endpoint_acceptAccepts that connection on the Atlas side. Set false when using peering.Like the interface endpoints, the Atlas PrivateLink endpoint takes one subnet per AZ from the blue private subnets.
SFTP
enable_sftp = true with sftp_server_type = "aws-tf" — AWS Transfer Family.enable_sftp = false and sftp_server_type = "none".Tags your existing subnets must already carry
When the modules create the VPC they tag the subnets themselves. With create_vpc = false nothing tags them for you, and both the load balancer controller and Karpenter find subnets by tag. Check these are present before you apply:
kubernetes.io/role/internal-elb = 1On every private subnet. This is what the modules set when they own the VPC.kubernetes.io/role/elb = 1On every public subnet. Only applies in private-public mode; public subnets are unused in private-private.karpenter_subnet_selector_tagsWhatever tag set you put in inputs.hcl must exist on the subnets you want Karpenter to provision nodes into. The example ships it commented out, so decide the tag deliberately and apply it to those subnets.karpenter_subnet_selector_tags = { "karpenter.sh/discovery" = "netlinq-ems-blue" }
Verify what is actually on the subnets before applying:
$ aws ec2 describe-subnets --subnet-ids subnet-aaa subnet-bbb subnet-ccc \ --query "Subnets[].{Id:SubnetId,Tags:Tags}"
Restricting who reaches the load balancers
The ingress load balancers filter on source IP, and the shipped default allows everything. In a private deployment, narrow it:
allowed_ingress_source_cidrs = ["10.0.0.0/8"] # default is ["0.0.0.0/0"]
Variant
Deploying into an existing VPC
To reuse a VPC you already have, tell the modules about it before Step 4 and let them skip VPC creation.
In example/base/output.tf, replace the computed outputs with your real IDs:
output "public_subnets" { value = ["subnet-23421312", "subnet-1232132312", "subnet-123131321"] } output "igw_id" { description = "The ID of the Internet Gateway" value = "igw-12313321321321232" }
Then in example/inputs.hcl, point a new state bucket at this configuration so it is tracked separately, and switch VPC creation off:
create_vpc = false # read only when create_vpc is false existing_vpc_id = "vpc-asdsadsdsad" existing_main_route_table_id = "rtb-asdsadadsadad" existing_private_route_table_ids = ["rtb-asdsadsa"] existing_vpc_cidr = "10.121.0.0/16" existing_vpc_subnets = ["subnet-asdadasdsad", "subnet-asdsadasd", "subnet-asdsaddsad"] existing_vpc_name = "netlinq-ems-vpc" # second path: add a CIDR block when the existing one is full enabled_additional_vpc_cidr = false additional_vpc_cidr = "" additional_vpc_private_subnet_cidr = []
existing_vpc_subnets takes private subnet IDs — the private ones from that VPC, not the public ones.Check the rest of the configuration still makes sense against the existing network, then start again from Step 4.
Teardown
Destroying the deployment
Delete the Kafka topics first, before any destroy that removes the cluster:
$ kubectl delete kafkatopic --all -n netlinq
Pick the scope
$ terragrunt run-all destroy --queue-exclude-dir=sftp --queue-exclude-dir=base \ --queue-exclude-dir=secrets --source-update
$ terragrunt run-all destroy --queue-exclude-dir=base --queue-exclude-dir=secrets \ --source-update
$ terragrunt run-all destroy --queue-exclude-dir=secrets --source-update
$ terragrunt run-all destroy
Keeping the VPC
To tear the base module down piece by piece and leave the VPC standing, target each sub-module:
$ terragrunt run-all destroy --queue-include-dir=base --source-update -target=module.mongo $ terragrunt run-all destroy --queue-include-dir=base --source-update -target=module.rds $ terragrunt run-all destroy --queue-include-dir=base --source-update -target=module.acm $ terragrunt run-all destroy --queue-include-dir=base --source-update -target=module.s3
Secrets
Secrets are left out of every command above on purpose. Destroy them only when you are sure nothing will be redeployed against them:
$ terragrunt run-all destroy --queue-exclude-dir=base --queue-include-dir=secrets \ --source-update