Prerequisites
Prerequisites
- Two Fjall applications, each with networking enabled. An application created without a VPC cannot peer.
- The 12-digit AWS account ID that owns each application.
- Commands run from the project root, so
fjall/<app>/infrastructure.tsresolves.
fjall add vpc-peer and fjall add vpc-peer-accepter edit infrastructure.ts. Nothing reaches AWS until you run fjall deploy, and the order of those two deploys matters.Peering has two halves
One peering connection is declared twice, once in each application. Neither half works alone.
The requester is the side that initiates the connection. The accepter is the side being reached. Both constructs land in their own application’s network stack.
The split exists because the two sides usually live in different AWS accounts. The accepter cannot create resources in the requester’s account, and the requester has no read access to the accepter’s VPC until the accepter grants it. The accepter half is what issues that grant.
What the accepter publishes
The accepter writes four SSM parameters under/fjall/<organisation>/<app>/, and attaches a resource policy granting each requester account ssm:GetParameter on them:
Required properties
Both halves take--app and --name like any other fjall add invocation. Each then has its own required properties.
--peerAppName is the accepter application’s Fjall name, exactly as it appears in fjall apps. It is the SSM prefix every remote lookup is built from, so a typo does not fail at add time. It builds a discovery path that nothing has written, and the requester’s deploy fails there instead.
--peerAccountId is the 12-digit AWS account that owns the accepter’s VPC.
--requesterAccountIds is a JSON array of the 12-digit accounts allowed to initiate peering. A value starting with [ must parse as JSON, so quote it for the shell:
fjall add refuses a missing --peerAppName or --peerAccountId before it writes anything. Omitting either would synthesise successfully and deploy a peering connection pointed at nothing.
Optional properties
The four
peer* overrides are independent. Supply the ones you know and the rest still resolve from SSM.
Deploy the accepter first, then the requester
This is the one ordering rule on the page:shared. The requester is api. The resource wizard prints the same pair as its next step after adding a peer, labelled Deploy accepter first, then requester, with each application name single-quoted.
Avoid naming an application
organisation, platform, account or domain. Those four words are reserved on the fjall deploy <app> and fjall destroy <app> positionals, so fjall deploy platform is refused before it validates anything and points you at the tier deploy instead.Same repository or separate repositories
Where both applications live decides how many files you edit.Both applications in one repository
Runfjall add twice, once per application, then deploy in accepter-then-requester order. Both infrastructure.ts files are on disk, so nothing is remote.
The Ink resource wizard, reached from the “add your first resource” hand-off shown after fjall create app for a custom-tier application, handles this case in one pass. It writes both halves atomically: if the second write fails, the first file is restored to its pre-edit content.
Applications in separate repositories
The CLI decides this by looking forfjall/<peer-app>/infrastructure.ts under your working directory. When that file is absent, only the local half can be written, and the wizard prints a companion command instead of the two-deploy sequence:
shared’s infrastructure.ts had both applications been in one checkout, because it is rendered from them.
On fjall 31 the wizard prints a shorter reminder carrying a --for flag that does not exist. Treat that line as a note of the half you still owe, and type the full invocation shown above in the other repository instead.
Adding the accepter locally reverses the halves: the printed command names vpc-peer and carries --peerAppName and --peerAccountId pointing back at the local application.
fjall add vpc-peer typed at a terminal always writes one statement into one application, whichever repository layout you have. The two-halves-in-one-pass behaviour belongs to the wizard alone.Cross-account and cross-region
Cross-account is the default assumption. The accepter’s role trusts whichever accounts you list in--requesterAccountIds, and the SSM resource policy grants those same accounts read access to the discovery parameters. Two applications in the same AWS account use the identical two halves, with your own account ID on both sides.
Cross-region needs one extra flag on the requester:
--peerAppName, so automatic lookup lines up when both applications belong to the same Fjall organisation. Where it cannot (a VPC imported from outside Fjall, or a peer in another organisation), supply the values directly:
Exposing resources across the peering
A peering connection carries traffic. It does not open a security group or tell the requester where anything lives.exposedResources on the accepter does both.
For each entry, the accepter opens ingress from the listed CIDRs to the resource’s port, and publishes the endpoint under /fjall/<organisation>/<app>/resources/<name>/:
An ECS entry needs the cluster to have an internal load balancer. Without one, synth fails naming the service. A duplicate
name and a malformed CIDR both fail at synth, by name. An empty allowedFromCidrs warns and publishes nothing, so nothing can reach the resource.
Reading the exposed resource from the requester
On the requester side,remoteConnections on an ECS service turns each exposed resource into a pair of environment variables read from those SSM parameters:
PLATFORM_PRIMARY_HOST and PLATFORM_PRIMARY_PORT into the container. The default prefix is the peer application name and the resource name in SCREAMING_SNAKE_CASE, and envPrefix overrides it. Full property reference: ECS Cluster.
Worked example
api in account 111111111111 needs to reach a database in shared in account 222222222222. Both applications live in one repository.
1
Add the accepter half
VpcPeerAccepterFactory.build("ApiSharedPeer", { requesterAccountIds: ["111111111111"] }) into fjall/shared/infrastructure.ts.2
Add the requester half
VpcPeerFactory.build("ApiSharedPeer", { peerAppName: "shared", peerAccountId: "222222222222" }) into fjall/api/infrastructure.ts.3
Confirm both halves are declared
4
Deploy the accepter, then the requester
shared deploy creates the accepter role and publishes the four discovery parameters. The api deploy then reads them, creates the peering connection, and writes routes on both sides.fjall list.
Troubleshooting
The requester deploy fails resolving SSM parameters
The accepter has not deployed, or deployed withpublishToSsm set to false. The requester’s network stack cannot resolve /fjall/<organisation>/shared/vpc-id and its three siblings, because nothing has written them.
Deploy the accepter application, then re-run the requester deploy. This is the usual way to hit trouble here, and the fix is always the same.
Nothing is reachable and no deploy failed
Check that both halves exist:The peering is up but traffic is refused
The connection and routes carry traffic. Security groups still have to allow it. Add the resource to the accepter’sexposedResources with the requester’s VPC CIDR in allowedFromCidrs, or open the ingress on the target resource yourself.
peerAppName points at the wrong application
--peerAppName builds the SSM lookup path. A wrong value fails at deploy time as a missing parameter, not as a name error. Check it against fjall apps and fix it with fjall modify:
Removing a peering
fjall remove takes one half at a time, and edits code rather than AWS:
Next Steps
fjall add reference
Every flag, property and per-type add contract.
Add Resources
How
fjall add edits infrastructure.ts, and the other eleven resource types.fjall deploy
Change plans, approval gates and live CloudFormation progress.
fjall remove
Take a peering half back out of infrastructure.ts.
ECS Cluster
remoteConnections, the consumer side of an exposed resource.VPC
CIDR ranges, subnets and the networking a peering connects.