ops.json in each
service that should be part of the cluster.
This page assumes your service is already deploying on LocalOps. If you’re still packaging your app, start with the
Elixir/Phoenix guide.
Enable it
ops.json
What LocalOps injects
Two environment variables are injected into your pods:app-services is the Kubernetes namespace all your services run in within the environment.
Wire up libcluster
Read the DNS name from the environment — don’t hardcode it.config/runtime.exs
Your release config stays yours
LocalOps does not setRELEASE_DISTRIBUTION, RELEASE_NODE, or RELEASE_COOKIE. Set these yourself — typically
RELEASE_DISTRIBUTION=name with RELEASE_NODE=myapp@${POD_IP}. POD_IP is already injected into every service, so you
can reference it directly.
Cluster across regions
The headless service above only resolves inside one environment’s Kubernetes cluster, so nodes in your Mumbai environment can’t see nodes in Ireland. To form a single mesh across regional environments, give each environment a DNS name in a private zone that every regional VPC can resolve, and let LocalOps keep that name pointed at the environment’s pods.The steps below use Mumbai (
ap-south-1) and Ireland (eu-west-1) as example regions. Substitute the regions,
CIDRs, and names that fit your setup.Spin up an environment in each region
In LocalOps, create an environment in every region the cluster should span — say one in
ap-south-1 (Mumbai) and one in eu-west-1 (Ireland) — and deploy the same services to each.Every environment gets its own VPC, and by default every VPC uses 10.0.0.0/16. Peered VPCs can’t have overlapping
CIDRs, so open Advanced options on the Create environment page and give each environment its own block — for
example 10.10.0.0/16 for Mumbai and 10.20.0.0/16 for Ireland.Set up VPC peering between the regions (AWS)
DNS gets nodes to each other’s addresses; peering is what gets packets there. For every pair of regions:
- Request the peering connection. In the VPC console of the first region (Mumbai), go to Peering connections → Create peering connection. Pick the Mumbai environment’s VPC as the requester, choose Another Region (and Another account, if the environments live in different AWS accounts), and enter the Ireland environment’s VPC ID.
- Accept it. Switch the console to the other region (Ireland), open Peering connections, select the pending request, and choose Actions → Accept request.
-
Update the private subnets’ route tables, on both sides. Your pods run in the environment’s private subnets,
so their route tables need a route to the peer VPC. In Route tables, filter by each environment’s VPC and edit
every route table associated with a private subnet — LocalOps creates one per availability zone — adding:
Leave the public subnets’ route tables alone — nodes never talk across regions from there.
Open BEAM ports in the EKS cluster security groups
With peering in place, packets reach the other VPC but are dropped at the pods unless the security group allows
them. Nodes need EPMD (TCP Then, in each region’s VPC console, open Security groups and filter by the environment’s VPC. LocalOps creates
a security group along with the EKS cluster, and its name contains
4369) plus the distribution ports open to the peer region.Erlang picks a random distribution port by default, so first pin it to a fixed range in your release config:config/runtime.exs
eks-cluster — that’s the group attached to
your pods. Add these inbound rules, with the peer region’s CIDR as the source:With more regions, add the same pair of rules for each peer region’s CIDR.
Create one private zone shared by every regional VPC
On AWS, create a Route 53 private hosted zone — say
mesh.acmecorp.internal — and associate it with the VPC of
every regional environment you spin up. One zone, many VPC associations. If your environments live in different AWS
accounts, use cross-account zone association. On GCP, the equivalent is a Cloud DNS private zone with each regional
VPC added to its visibility list.The zone has to be associated with a region’s VPC before nodes there can resolve peers.Give each environment a name in ops.json
Alongside Keep
beam_cluster_id, set beam_dns_name to the record this environment should own inside that zone:ops.json
beam_cluster_id identical everywhere — it’s still what makes these nodes one cluster. beam_dns_name is
per environment: mumbai.mesh.acmecorp.internal in Mumbai, ireland.mesh.acmecorp.internal in Ireland, and so on.LocalOps creates that record and keeps it in sync — it publishes the IPs of every pod in the environment carrying
that beam_cluster_id, and updates the record as pods scale, restart, or get rescheduled. You don’t manage the
record set yourself.List every region in your topologies
Add one topology per region so libcluster polls all of them. The same config ships to every region — each node finds its local peers through
LOPS_BEAM_CLUSTER_DNS still points at the local
headless service, so keep using it for this environment’s own nodes and name the peer regions by their
beam_dns_name:config/runtime.exs
LOPS_BEAM_CLUSTER_DNS and every
remote region through that region’s record. Nodes connect from both ends, and the mesh forms.Verify the mesh from one region
Once the new build is deployed in every region, use From that shell, attach a remote console to the running release and list the connected nodes:You should see nodes from every region, not just this one — for example
ops connect to open a
shell on a pod in one region’s environment:b2bapp@10.10.x.x alongside
b2bapp@10.20.x.x. If only local nodes show up, recheck the peering routes and the security group rules above.Preview environments ignore
beam_dns_name. They keep forming their own single-region cluster, for the same reason
they stay out of your production cluster.Things to know
It takes effect on your next build
LocalOps snapshotsops.json when it builds your service, so redeploying your current image won’t pick up a newly added
beam_cluster_id. Push the change and let it build.
Preview environments get their own cluster
A preview environment forms its own separate cluster and never joins your production cluster. This is deliberate — otherwise a preview would share your cookie and be able to call into live nodes. See ephemeral databases and services for pull request previews.Job and cron services join too
Job and cron services join the cluster as well, so scheduled and one-off tasks can call into the running cluster. If you’d rather they stayed out, just leavebeam_cluster_id out of those services’ ops.json.
Processes with their own image stay out
Aprocesses entry that specifies its own image stays out of
the cluster, since it isn’t running your app.
Verify it worked
Get a shell on a running pod withops connect, open a remote console, and run:
LOPS_BEAM_CLUSTER_NAME.
To watch inter-node distribution traffic over time, see the
Erlang & Elixir instrumentation guide — the Erlang-Distribution Grafana
dashboard it links is built for exactly this.