# Deployment
How to stand up a new app in [`infra`](https://github.com/FSHTech/infra).
## Demo (preview)
For a demo app the whole platform already exists in the
`development` account, so onboarding is straightforward.
### 1. Add the repo to `repos.hcl`
```hcl
# infra/repos.hcl
demo_repos = [
"demo-example",
"demo-newthing", # <- the list entry IS the GitHub repo name
]
```
That single line does three things via existing fan-out units:
- **creates the GitHub repo** (`demo_repos` → `catalog/github/demo-repos`, open-push, no branch protection),
- **makes it a preview app** (it joins the `preview_repos` union → Cloudflare Access app + OIDC trust),
- **sets its CI vars** (`PREVIEW_AWS_ROLE_ARN` via `preview_repo_vars`, `CODEARTIFACT_*` via `codeartifact_repo_vars`).
### 2. Merge
Make an `infra` PR, get a review, merge.
On merge, everything will automatically happen.
### 3. Commit two files to the app repo
`preview.toml` at the repo root:
```toml
[preview]
app = "demo-newthing" # DNS slug + ECR image-tag prefix
worker_entrypoint = "tracker.queue.main:main" # the pgq worker's module:function
db_name = "tracker" # Postgres DB name
base_branches = ["main"] # branches that get a persistent trunk
```
`.github/workflows/preview.yml` — copy `demo-example`'s verbatim.
---
## Production
A production app has a fully highly available stack.
### 1. Add the repo to `repos.hcl`
```hcl
# infra/repos.hcl
prod_repos = [
"example",
"sales-pipeline",
"new-locality", # <- the live/internal/
name; its env.hcl supplies app_name
]
```
### 2. Vend the AWS account (`live/management`)
```hcl
# infra/live/management/terragrunt.stack.hcl
unit "account_new_locality" {
source = "${get_repo_root()}/catalog/aws/account"
path = "account-new-locality"
values = {
name = "new-locality"
ou = "internal" # or "customers" for a customer account
client = true
}
}
```
### 3. Create the environment config (`live/internal/new-locality/`)
This is "the specific environment configuration." **Two files, and the only one
you edit is `env.hcl`.**
`env.hcl` — the three knobs at the top are all you set; the rest are org defaults
plus the few keys other files read. Purely-derived values (`github_repo`,
`customer`) live in the shared `terragrunt.stack.hcl`, not here:
```hcl
# infra/live/internal/new-locality/env.hcl
locals {
# ── Per-app knobs: the only lines you change for a new app ───────────────────
environment = "new-locality" # dir name = AWS account = customer label
app_name = "new-locality" # GitHub repo under FSHTech/, ECR/ECS name prefix
worker_entrypoint = "app.queue.main:main" # the pgq worker's module:function
# ── Org defaults / contract keys other files read; leave as-is ───────────────
aws_region = "us-east-2"
secondary_regions = ["us-west-2"]
deploy_role = "OrganizationAccountAccessRole"
github_owner = "FSHTech"
customer_zone = "${local.environment}.app.efesaitch.com"
tags = {
Environment = local.environment
ManagedBy = "terragrunt"
}
}
```
```bash
cp live/internal/example/terragrunt.stack.hcl live/internal/new-locality/terragrunt.stack.hcl
```
That will give you a starting point; `example` has a full production deployment.
### 4. Delegate the DNS subzone (`live/domain`)
Gives the app's own Route 53 zone (`new-locality.app.efesaitch.com`) authority, so its
ACM certs can validate. The unit name must be `-subzone-delegation`
— that's the exact path `env.hcl`'s derived `acm_delegation_path` points at.
```hcl
# infra/live/domain/terragrunt.stack.hcl
unit "new_locality_subzone_delegation" {
source = "${get_repo_root()}/catalog/aws/route53-delegation"
path = "new-locality-subzone-delegation"
values = {
aws_region = local.env.aws_region
j parent_zone_path = "../route53-zone"
subzone_name = "new-locality.${local.env.delegated_zone}"
child_zone_path = "${get_repo_root()}/live/internal/new-locality/.terragrunt-stack/shared/.terragrunt-stack/route53-zone"
}
}
```
### 5. Register the repo + web vars (`live/source-code`)
Two units: the GitHub repo itself, and the FE (S3/CloudFront) deploy vars. The
`web_vars` unit reuses the `prod_web_vars[""]` map that's derived
automatically from `prod_repos` + `env.hcl`, so you just reference it by name.
```hcl
# infra/live/source-code/terragrunt.stack.hcl
unit "new_locality" {
source = "${get_repo_root()}/catalog/github/repo"
path = "new-locality"
values = {
repo_name = "new-locality"
description = "New locality app"
teams = local.repo_teams
push_allowed_teams = local.push_allowed_teams
code_owner_teams = local.code_owner_teams
merge_bypass_teams = ["engineers-fte"]
depends_on = local.team_units
}
}
unit "new_locality_web_vars" {
source = "${get_repo_root()}/catalog/github/web-deploy-vars"
path = "new-locality-web-vars"
values = local.prod_web_vars["new-locality"]
}
```
The `CODEARTIFACT_*` and (if you also want previews) `PREVIEW_AWS_ROLE_ARN` repo
vars are already handled — they fan out from the derived `codeartifact_consumer_repos`
/ `preview_repos` sets. The BE `deploy-role` + `deploy-repo-vars` are part of the
per-app stack you copied in step 3.
### 6. Grant human access (`roster.hcl`)
Add the new env to each role's `aws_access` map that should reach it:
```hcl
# infra/roster.hcl (inside locals.roles)
engineers-fte = { aws_access = { ..., "new-locality" = "Admin" } }
operations-fte = { aws_access = { ..., "new-locality" = "AppMaintainStaging" } }
```
### 7. Merge
Make an `infra` PR, get a review, merge.
On merge, everything will automatically happen.