Expose Your Application

Expose Your Application

Your application is running on the cluster (see Deploy Your Application). Now you need users to reach it.

This golden path takes you from a running pod to a publicly accessible, TLS-secured application with automatic DNS and TLS management.

By the end of this path, your application will be:

  • Reachable at https://your-domain.example.com
  • Secured with a TLS certificate from Let’s Encrypt (auto-renewed)
  • DNS records created and managed automatically
  • HTTP traffic redirected to HTTPS by default

How it works

Three components work together, all managed by Skyscrapers on the platform side:

ComponentWhat it does
Traefik (or AWS Load Balancer Controller)Routes external traffic to your pods
cert-managerIssues and renews TLS certificates via Let’s Encrypt
ExternalDNSCreates DNS records in Route53 automatically from your Ingress

You create an Ingress resource. The rest happens automatically.

Choose your ingress controller

The platform supports two ingress controllers. Choose based on your needs:

Use Traefik (recommended for most cases) when you need:

  • CORS headers at the ingress level
  • Rate limiting without additional AWS costs
  • Complex proxy timeout or buffering requirements
  • Cookie-based session affinity
  • Middleware composition (chain multiple behaviors)

Use AWS Load Balancer Controller when you need:

  • Native OIDC or Cognito authentication at the load balancer
  • AWS WAF, Shield, or GuardDuty integration
  • An AWS-managed service with SLA guarantees
  • An origin for CloudFront, when you’re already using CloudFront in front of your application

For a full comparison and decision guidance, see the Ingress overview.

Note

ingress-nginx is sunsetting. Do not use it for new ingresses. If you’re currently using it, see the Ingress overview for migration guidance.

The simple path: Traefik with automatic TLS and DNS

This is the golden path, one Ingress resource gives you TLS, DNS, and HTTPS redirection automatically.

Step 1: Add an Ingress to your Helm chart

Add templates/ingress.yaml to your Helm chart:

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    {{- with .Values.ingress.annotations }}
    {{- toYaml . | nindent 4 }}
    {{- end }}
spec:
  ingressClassName: {{ .Values.ingress.className }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType | default "Prefix" }}
            backend:
              service:
                name: {{ include "my-app.fullname" $ }}
                port:
                  number: {{ $.Values.service.port }}
          {{- end }}
    {{- end }}
  tls:
    - secretName: {{ include "my-app.fullname" . }}-tls
      hosts:
        {{- range .Values.ingress.hosts }}
        - {{ .host | quote }}
        {{- end }}
{{- end }}

Step 2: Add ingress values

In your Helm chart’s values.yaml, add the defaults:

ingress:
  enabled: false
  className: traefik
  annotations: {}
  hosts: []

Then in your environment’s helmrelease-patch.yaml, enable and configure it:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: my-app
  namespace: flux-apps
spec:
  values:
    ingress:
      enabled: true
      className: traefik
      hosts:
        - host: my-app.example.com
          paths:
            - path: /
              pathType: Prefix

Step 3: Push and verify

Push your changes. Flux deploys the Ingress, then:

  1. cert-manager sees the cert-manager.io/cluster-issuer: letsencrypt-prod annotation and requests a certificate from Let’s Encrypt using DNS-01 validation via Route53
  2. External DNS sees the Ingress hostname and creates an A record in Route53 pointing to the Traefik load balancer
  3. Traefik routes traffic to your pods and automatically redirects HTTP to HTTPS

Verify:

# Check the Ingress is created
kubectl get ingress -n flux-apps

# Check the certificate was issued
kubectl get certificate -n flux-apps

# If the certificate is stuck, check challenges
kubectl get challenges -n flux-apps

# Test it
curl -I https://my-app.example.com

Certificate issuance typically takes 1-2 minutes. DNS propagation may take a few minutes more.

That’s it for the standard case. The rest of this page covers common variations.


Internal-only access

For services that should only be reachable within the VPC (not from the internet), use the internal Traefik instance:

ingress:
  enabled: true
  className: traefik-internal
  hosts:
    - host: my-app-internal.example.com
      paths:
        - path: /
          pathType: Prefix

Note

Internal ingresses still get public DNS records (resolving to private IPs). The certificate will appear in public Certificate Transparency logs. This is expected behavior, the service is still only reachable from within the VPC.

Using the AWS Load Balancer Controller instead

If you need AWS-native load balancing (OIDC auth, WAF, etc.), use the ALB controller:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:eu-west-1:123456789:certificate/abc-123
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/ssl-redirect: "443"
spec:
  ingressClassName: alb
  rules:
    - host: my-app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app
                port:
                  number: 80

Important

The ACM certificate referenced in alb.ingress.kubernetes.io/certificate-arn must exist before you create the Ingress. Unlike the Traefik path, certificates are not issued automatically: set it up beforehand via IaC (Terraform) or ask your Skyscrapers customer lead.

Key differences from Traefik:

TraefikALB Controller
TLS certificatescert-manager (Let’s Encrypt, auto-renewed)ACM (AWS Certificate Manager, via Terraform, ask your customer lead in Skyscrapers)
IngressClassNametraefik or traefik-internalalb
Target routingVia Traefik proxy podsDirect to pod IPs (target-type: ip)
CostIncluded in platformPer ALB + LCU usage
CORSNative middlewareRequires CloudFront or app-level handling
Rate limitingNative middlewareRequires AWS WAF

Important

When using ALB, you can group multiple Ingress resources into a single ALB to reduce costs:

annotations:
  alb.ingress.kubernetes.io/group.name: shared-alb
  alb.ingress.kubernetes.io/group.order: "1"

For the full ALB configuration reference, see ALB Ingress.

Common Traefik patterns

Traefik handles CORS, rate limiting, redirects, and similar behaviors through Middlewares: separate Kubernetes resources that you create in your namespace and reference from your Ingress via an annotation:

annotations:
  traefik.ingress.kubernetes.io/router.middlewares: my-namespace-cors@kubernetescrd

The reference format is <namespace>-<name>@kubernetescrd. You can chain multiple middlewares by comma-separating them; they execute in order.

All patterns are documented with full examples in the Traefik reference:

Delegated domains

If your domain’s DNS is hosted externally (not in the same AWS account), cert-manager can still issue certificates. Create a CNAME in your external DNS:

_acme-challenge.api.example.com  IN  CNAME  _acme-challenge.api.production.eks.example.org.

Where production.eks.example.org is the cluster’s domain. Then create the Ingress as normal, cert-manager follows the CNAME to validate ownership.

Wildcard certificates

For multiple subdomains under the same domain, use a wildcard certificate to avoid hitting Let’s Encrypt rate limits:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-example-com
  namespace: flux-apps
spec:
  secretName: wildcard-example-com-tls
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-prod
  dnsNames:
    - "example.com"
    - "*.example.com"

Then reference the same secret across multiple Ingresses:

tls:
  - secretName: wildcard-example-com-tls
    hosts:
      - "*.example.com"

Note

A TLS Secret is scoped to a single namespace. To use the same wildcard in another namespace, cert-manager will request a new certificate.

Disabling external-dns

If you’re using an external CDN (CloudFront, Cloudflare) and manage DNS yourself, disable automatic DNS record creation:

annotations:
  external-dns.alpha.kubernetes.io/exclude: "true"

Disabling HTTPS redirection

By default, Traefik redirects all HTTP traffic to HTTPS. If you need to serve plain HTTP (rare), create an IngressRoute with a higher priority than the default redirection rule, see Disable automatic HTTPS redirection.

Troubleshooting

Certificate not issuing

# Check certificate status
kubectl describe certificate my-app-tls -n flux-apps

# Check if challenges are stuck
kubectl get challenges -n flux-apps

# Check cert-manager logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager

Warning

cert-manager has a limit of 60 parallel challenges. If this fills up with unprocessable challenges (DNS misconfiguration), further certificate issuance is blocked. Monitor with kubectl get challenges -A.

DNS not resolving

# Check if external-dns created the record
kubectl logs -n infrastructure -l app.kubernetes.io/name=external-dns | grep my-app.example.com

# Verify the record in Route53
aws route53 list-resource-record-sets --hosted-zone-id ZXXXXX | grep my-app

Ingress not routing traffic

# Check the Ingress is created and has an address
kubectl get ingress -n flux-apps

# Check Traefik is seeing the Ingress
kubectl logs -n traefik -l app.kubernetes.io/name=traefik | grep my-app

Further reading

Last updated on