For the complete documentation index, see llms.txt. This page is also available as Markdown.

PrivateLink to Your Systems

Give your Lunar Dedicated install a private path to internal systems (an internal Git server, registry, or API) by publishing them as an AWS PrivateLink endpoint service.

A Lunar Dedicated install reaches outwards to the systems it works with. It calls your Git platform to read repositories and post results, and its collectors, catalogers, and policies reach whatever they gather data from, such as registries, ticketing systems, and internal APIs. When one of those isn't reachable from the public internet, Lunar needs a private path to it.

This page is the setup guide for that path. It's written for whoever runs your AWS networking. You can forward it on its own.

This page covers Lunar reaching out to your systems. Reaching the Lunar hub in from your CI and browsers is a separate concern, covered in the Overview.

Do you need this?

Four questions decide it. Answer them before doing any work.

1. Is the target actually unreachable from the internet? Your Lunar install has a single, stable outbound IP address. If your service is internet-facing and you can allowlist one IP, that's far less work than everything below. Ask Earthly for the address.

2. Can you publish TXT records on a publicly-resolvable domain? AWS requires proof of domain ownership before it will attach a hostname to an endpoint service. Verification is scoped to the parent domain, so a name under example.com works for any subdomain. If your internal hostname sits under a suffix you can't prove ownership of (something like .corp or .internal with no public zone), tell Earthly, because there's a fallback for that case (Earthly overrides DNS inside the install instead) and it changes what you build here.

3. Can the service present a publicly-trusted TLS certificate? Lunar connects with hostname verification on, so the certificate must be valid for the hostname in question 2 and trusted by Lunar's workloads.

If you control the public zone from question 2, you can almost certainly get a public certificate for this name: DNS-01 validation only needs a TXT record, so the name never has to resolve publicly. Terminating TLS at the load balancer with an ACM certificate works the same way.

If the service can only present a certificate from your internal CA, tell Earthly. We'll need your CA's root and intermediate certificates (the public ones, never a private key) to add to the trust store Lunar's workloads use. Flag it too if your CA's revocation endpoints (CRL or OCSP) are only reachable inside your network, since Lunar won't be able to reach them.

4. How many distinct hostnames does Lunar need to reach? Not systems; hostnames. Remember to count your Git platform if it's self-managed and internal. An endpoint service can carry exactly one private DNS name, though that name may be a wildcard (*.internal.example.com). Three hostnames means three endpoint services, a gateway doing path-based routing under a single hostname, or a wildcard name in front of a gateway that routes by hostname.

How it works

You publish your internal service as an endpoint service. Earthly creates a matching interface endpoint inside the Lunar install's VPC. Lunar then reaches your service by its normal hostname, over a private AWS path.

Three properties are worth knowing up front, because they answer most security-review questions:

  • The connection is one-way. Lunar initiates every session. Nothing in your account can initiate a connection back through it.

  • Neither side learns the other's addresses. PrivateLink does not join the networks, add routes, or expose either CIDR range. Overlapping address ranges are fine.

  • Lunar can reach only the service you publish. Not the VPC it lives in, not anything else in your account.

Checklist

If you've published endpoint services before, this is all you need. The Prefer Terraform? section has the resource definition.

  1. Internal NLB in front of the service, targets healthy.

  2. Endpoint service backed by that NLB.

  3. Allowed principal: arn:aws:iam::<DEDICATED_ACCOUNT_ID>:root (Earthly provides the ID).

  4. Acceptance: automatic. See step 4 before choosing manual; it requires extra steps.

  5. Private DNS name: your service's real TLS hostname. Publish the TXT record, wait for verified.

  6. Send Earthly the values in What to send back.

Three details that are easy to miss:

  • Each endpoint service gets its own TXT verification name and value, so an already-verified domain does not carry over from another service.

  • The private DNS name must be the hostname on your service's TLS certificate, or Lunar's requests will fail hostname verification. If that certificate comes from an internal CA rather than a public one, Earthly needs your CA certificates too.

  • Availability zones must be exchanged as zone IDs, not zone names (see step 6).

What you need from Earthly first

You need two values from Earthly. The region is fixed per install and agreed up front; whoever forwarded you this page will have it. Everything you build here must be in the same region as the install.

The other is the dedicated account ID, used for the allowed principal in step 3. It's needed at just that one step, so if you don't have it yet, start building anyway and slot it in when it arrives.

Step 1 — Put the service behind an internal Network Load Balancer

An endpoint service can only be backed by a Network Load Balancer, so the service needs one in front of it.

  • Scheme: internal. PrivateLink reaches an internal NLB fine, and an internet-facing one would expose the service publicly, defeating the point.

  • Add a TCP listener on the port Lunar will use: 443 unless you tell Earthly otherwise.

  • Register your service as a target and confirm the targets are healthy before continuing.

An unhealthy target group produces a connection that looks fine on both sides and still refuses traffic. The endpoint will happily reach Available with a completely broken backend.

Note the availability zones your NLB is in, as zone IDs (euw2-az1) rather than zone names (eu-west-2a), because names don't mean the same thing in Earthly's account. Endpoint services are only reachable in the zones their load balancer covers, and Earthly's endpoint has to land in a zone you support. You'll send these in step 6.

Step 2 — Create the endpoint service

In the AWS console, go to VPC → Endpoint services → Create endpoint service. Not the load balancer screens; this is its own top-level resource.

  • Load balancer type: Network

  • Select the NLB from step 1

  • Require acceptance for endpoint: the console asks now; automatic acceptance (box unchecked) is recommended

AWS generates a service name that looks like:

That string is the value Earthly actually uses to connect. Keep it.

Step 3 — Allow Earthly's dedicated account

On the endpoint service, open Allow principals and add:

This permits principals in that one account to request a connection to this one service. It grants no IAM credentials, no assumable role, and no access to anything else in your account. A request from any other account is rejected by AWS before it ever appears in your console.

Step 4 — Choose automatic or manual acceptance

Automatic acceptance is strongly recommended. Earthly's endpoint connects as soon as it's created, and private DNS is configured in the same step. Your access control is the allowlist from step 3, which you've already set; automatic acceptance widens nothing.

Manual acceptance adds a human approval for each connection. Someone on your side approves it at VPC → Endpoint services → your service → Endpoint connections, selects the endpoint ID Earthly sends, and chooses Accept endpoint connection request. The CLI equivalent is aws ec2 accept-vpc-endpoint-connections. It's a single approval, with nothing to configure.

Step 5 — Associate and verify the private DNS name

This step is what lets Lunar call your service by its real hostname, with working TLS. Skipping it means Lunar can only reach an AWS-generated hostname that your certificate won't match.

5a. Associate the name. On the endpoint service, set private DNS name to your service's canonical hostname: the name on its TLS certificate, for example service.internal.example.com.

Use a fully-qualified name. A bare single-label name (just gitlab) will not work.

5b. Publish the verification record. AWS returns a TXT record name and value. Find them under Domain verification name and Domain verification value on the service's Details tab, or:

Create that TXT record in the public DNS zone for the domain:

Name
Type
Value

_a1b2c3d4e5f6g7h8.example.com

TXT

vpce:AbCdEf0123456789

An already-verified domain does not carry over. Each endpoint service gets its own verification name and value, so if you've published private DNS names for other services on this domain you still need a new TXT record for this one.

5c. Wait for verification. Check Domain verification status on the Details tab. If it stays pending, use Actions → Verify domain ownership for private DNS name to retry. DNS changes can take up to 48 hours to propagate, though it's usually much faster.

Do not tell Earthly you're ready until the status reads verified. Earthly's endpoint is created with private DNS enabled, and that request is rejected against a service whose name isn't verified yet.

Prefer Terraform?

Steps 2–5 are one resource. This is the recommended minimal shape if your networking is Terraform-managed:

The TXT record from step 5 is still published separately, through your DNS provider or an aws_route53_record if the zone lives in Route53.

Step 6 — What to send back

Send Earthly these six values:

Value
Example

Endpoint service name

com.amazonaws.vpce.eu-west-2.vpce-svc-0123456789abcdef0

Verified private DNS name

service.internal.example.com

TCP port

443

Region

eu-west-2

Acceptance

automatic, or manual

Supported availability zone IDs

euw2-az1, euw2-az2

If you flagged an internal CA in question 3, include your CA certificates as well. Earthly needs nothing further to create its side of the connection.

What happens next

From your six values, Earthly creates the interface endpoint, plus a security group that allows only the port you specified, and only from Lunar's workloads.

If you chose manual acceptance, Earthly sends you the endpoint ID at this point and waits for your approval, then makes a second change to enable private DNS.

An endpoint reaching Available proves less than it sounds: it means the PrivateLink connection was established, not that anything behind it works. So Earthly checks two things from a real Lunar workload:

  • The hostname resolves to the endpoint's private addresses. This is what proves traffic is taking the private path, rather than resolving to a public address and quietly bypassing the endpoint.

  • TLS completes with hostname verification enabled. A successful handshake exercises the whole path, so it also confirms your load balancer targets are healthy and your certificate covers the name.

Earthly then runs a collection against your service and confirms the data lands. It's the same end-to-end check every install gets, private or not.

How DNS resolves

Worth understanding if you're reviewing this, because nothing about it is visible in your account.

When Earthly creates the interface endpoint, AWS reads the verified private DNS name from your endpoint service and creates a hidden, AWS-managed private hosted zone associated with the Lunar VPC. That zone maps your hostname to the endpoint's network interfaces.

The consequence is split-horizon resolution: inside the Lunar install, service.internal.example.com resolves to the private endpoint. Everywhere else in the world, it resolves however it always did. Lunar's configuration contains an ordinary hostname and an ordinary HTTPS URL; nothing in Lunar knows PrivateLink is involved.

Troubleshooting

Earthly's endpoint is stuck in PendingAcceptance. Manual acceptance is enabled and nobody has approved the connection. Approve it under Endpoint connections on your service.

Earthly reports the connection request failed outright. Usually, the allowed principal is incorrect. Check the account ID in step 3 matches the one Earthly gave you exactly. An unlisted account can't see the service at all, so the failure happens on Earthly's side and never reaches your console.

Endpoint creation fails with a private DNS error. An error saying private DNS requires an accepted connection means manual acceptance is on. Approve the pending connection; Earthly completes the second step. See step 4.

Endpoint creation fails on availability zones. Earthly's endpoint has to be placed in zones your service supports. Send the zone IDs from step 6 (zone names don't translate across accounts) and Earthly will constrain placement.

The endpoint is Available but requests fail or hang. Check your NLB target group health first. Endpoint state reflects the PrivateLink connection, not your backend.

Requests connect but TLS fails. The hostname Lunar is calling isn't covered by your certificate's subject alternative names. The private DNS name in step 5 must be a name the certificate actually presents.

Domain verification won't complete. Check whether your DNS provider allows underscores in record names; if not, you can omit the AWS-supplied prefix and use the bare domain. Also check that your provider hasn't appended the domain a second time, or lowercased the value; AWS matches it exactly.

Last updated