Skip to main content
  • 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.ts resolves.
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:
The requester reads all four at deploy time. That read is the reason for the deploy ordering below.

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:
The accepter is 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.
The reason is the SSM read. The requester’s network stack resolves the accepter’s VPC ID, CIDR, peering role ARN and route table IDs from the four parameters above, and CloudFormation resolves them when it applies the stack. Deploy the requester first and those parameters do not exist yet, so the stack fails there. The accepter’s role also has to exist before the requester’s connection can be accepted in the peer account.
Reverse the order to unwind. The requester’s return-route handler assumes the accepter’s role to delete the routes it created, so remove and deploy the requester half before the accepter role goes away.

Same repository or separate repositories

Where both applications live decides how many files you edit.

Both applications in one repository

Run fjall 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 for fjall/<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:
From fjall 32, that is the command to run, as printed, from the other repository. It carries the same properties the wizard would have written into 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:
The SSM discovery path is built from the requester’s own organisation ID plus --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:
Setting --publishToSsm false on the accepter stops all four parameters being written and drops the cross-account read policy with them. Every requester peering with that application then has to supply all four values explicitly.

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>/:
Two variants exist. 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.
That flag emits a template, not a finished statement. Each entry’s resource is written as the resource’s name in string form, and the construct needs the construct reference. Open infrastructure.ts and swap the string for the resource before deploying, or synth fails with exposedResource 'Primary' has no 'serviceName' (database variant) but its resource is not IRelationalDatabase.

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:
That injects 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

This writes VpcPeerAccepterFactory.build("ApiSharedPeer", { requesterAccountIds: ["111111111111"] }) into fjall/shared/infrastructure.ts.
2

Add the requester half

This writes 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

The 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.
Giving both statements the same resource name is a convention rather than a requirement. The wizard does it, and it makes the pair easy to find with fjall list.

Troubleshooting

The requester deploy fails resolving SSM parameters

The accepter has not deployed, or deployed with publishToSsm 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:
An accepter with no matching requester deploys cleanly. It creates a role and four parameters, and no connection. There is no error to read, because nothing failed.

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’s exposedResources 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:
Keep that order. The requester’s teardown assumes the accepter’s role to delete the return routes it created in the peer account.

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.