Serve every tool on deployshed.com instead of nip.io

Harbor, Gitea, Argo CD, Jenkins, Vault, Grafana and vmui now answer on
their deployshed.com names alone. Each was already serving both while the
move was proved out; this removes the nip.io half.

The dual-hostname workarounds go with it. Jenkins' secondaryingress existed
only because its chart's primary ingress takes one hostName and a
certificate could not span both names — the real domain moves onto the
primary with jenkins-tls, which it already holds. Argo CD gets extraTls
rather than ingress.tls, because the boolean hardcodes secretName
argocd-server-tls and would request a second certificate for a name that
already has a valid one in argocd-deployshed-tls.

Harbor also changes in two ways beyond the hostname:

  - externalURL moves to https://harbor.infra.deployshed.com. Harbor hands
    this to docker clients in its own API responses and builds the push
    commands shown in its UI from it, so a stale value is what makes a
    correctly-configured registry still advertise the old address.

  - updateStrategy is now Recreate. Its jobservice and registry volumes are
    standard-rwo (ReadWriteOnce), and a RollingUpdate starts the new pod
    before the old one releases the disk, so the replacement hangs forever
    on Multi-Attach. The cluster was sitting in exactly that state, old pods
    serving while new ones stayed in ContainerCreating. The chart's own
    comment on this value recommends Recreate when RWM is unavailable. The
    cost is a brief outage during upgrades, which beats a rollout that
    cannot complete.

The private registry CA is not removed yet. Apps deployed before this move
recorded nip.io image references that only change when each is rebuilt, so
the old hostname stays served by a standalone Ingress until then.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEsTefWWifp4ikvhHF5s6N
This commit is contained in:
Mukul Sharma
2026-09-17 09:31:26 +05:30
co-authored by Claude Opus 5
parent df3e0a41af
commit 61bc4af1a0
8 changed files with 106 additions and 75 deletions
@@ -81,21 +81,23 @@ argo-cd:
ingress:
enabled: true
ingressClassName: contour
hostname: "argocd.35.238.248.203.nip.io"
# The chart supports additional hostnames natively, so the new domain
# is served here rather than from a second Ingress object.
extraHosts:
- name: "argocd.infra.deployshed.com"
path: /
# No TLS yet, deliberately. This chart's ingress.tls is a boolean, not
# a host list: turning it on requests ONE certificate covering
# `hostname` plus every extraHost, and Let's Encrypt cannot issue for
# nip.io — so the request would fail and neither name would be served
# over TLS. There is no extraTls to scope it more narrowly.
hostname: "argocd.infra.deployshed.com"
# TLS via extraTls rather than the `tls: true` boolean, deliberately.
#
# This one gets its certificate when nip.io is retired and `hostname`
# itself becomes the deployshed.com name. Until then ArgoCD is HTTP
# only, as it already was.
# The boolean hardcodes `secretName: argocd-server-tls` (see the
# chart's argocd-server/ingress.yaml). This deployment already holds a
# valid, issued certificate for this exact hostname in
# argocd-deployshed-tls, created by the standalone Ingress that served
# the real domain while nip.io was still on `hostname`. Flipping the
# boolean would ignore that and request a second certificate for the
# same name — a needless issuance and a gap while it is obtained.
#
# extraTls takes an explicit secretName, so the existing certificate is
# adopted as-is and the standalone Ingress can simply be deleted.
extraTls:
- hosts:
- argocd.infra.deployshed.com
secretName: argocd-deployshed-tls
resources:
requests:
cpu: 50m
@@ -129,7 +131,10 @@ argo-cd:
params:
server.insecure: true
cm:
url: "http://argocd.35.238.248.203.nip.io"
# https now that the only hostname served carries a real certificate.
# This is what ArgoCD builds its own links from, so leaving it http
# would hand out plain-HTTP URLs for a TLS-only deployment.
url: "https://argocd.infra.deployshed.com"
timeout.reconciliation: 3m
timeout.reconciliation.jitter: 60s
# No Ingress health override, unlike the homelab: there Contour sat
@@ -35,10 +35,16 @@ contour:
type: LoadBalancer
# The reserved address from Terraform (module.network's
# google_compute_address). Every hostname in this deployment is
# <name>.35.238.248.203.nip.io, so this pin is what makes DNS work at
# all an unpinned Service takes a fresh ephemeral IP and every
# hostname points at nothing.
# google_compute_address). Every hostname in this deployment — the
# deployshed.com records, including the two wildcards — resolves here,
# so this pin is what makes DNS work at all: an unpinned Service takes
# a fresh ephemeral IP and every hostname points at nothing.
#
# More load-bearing now, not less, than when hostnames were
# <name>.35.238.248.203.nip.io. Those encoded the address, so a changed
# IP produced names that were merely wrong. Real DNS records point here
# until somebody edits them in Cloudflare, so a changed IP is an
# outage across every hostname at once.
#
# spec.loadBalancerIP is deprecated upstream (Kubernetes 1.24), and
# GKE's replacement is the annotation
@@ -43,7 +43,7 @@ gitea:
actions:
ENABLED: true
server:
ROOT_URL: http://gitea.35.238.248.203.nip.io/
ROOT_URL: https://gitea.infra.deployshed.com/
service:
# The homelab sat on a LAN; this Gitea is on a public IP. Open
# registration would let anyone on the internet create an account.
@@ -69,7 +69,7 @@ gitea:
# The proper fix is a configurable webhook base URL in toolshed
# pointing at toolshed-api.toolshed.svc.cluster.local:8080, after
# which this entry should be removed.
ALLOWED_HOST_LIST: private,toolshed.35.238.248.203.nip.io
ALLOWED_HOST_LIST: private,console.deployshed.com
admin:
username: gitadmin
# Created by hand with kubectl at bootstrap, because Vault and ESO
@@ -82,22 +82,20 @@ gitea:
# Contour does not exist yet at bootstrap — the Ingress just sits unused
# until ArgoCD installs it.
#
# Two hosts while the deployment moves onto its own domain. The nip.io one
# stays until everything that references it has been repointed — ROOT_URL
# below, and any git remote anyone has configured.
# One host. The nip.io name was served alongside this one while the
# deployment moved onto its own domain, and came out once everything
# referencing it had been repointed: ROOT_URL above, the webhook allow-list
# above that, and any git remote anyone had configured.
ingress:
enabled: true
className: contour
annotations:
# Issues the certificate named in tls below. Only the real domain is
# listed there: Let's Encrypt cannot issue for nip.io, and asking for
# one certificate spanning both names returns nothing for either.
# Issues the certificate named in tls below. This could only ever cover
# the real domain: Let's Encrypt cannot issue for nip.io, so while both
# names were served, asking for one certificate spanning them returned
# nothing for either.
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: gitea.35.238.248.203.nip.io
paths:
- path: /
pathType: Prefix
- host: gitea.infra.deployshed.com
paths:
- path: /
@@ -268,13 +268,14 @@ grafana:
enabled: true
ingressClassName: contour
annotations:
# Only the real domain appears in tls below — Let's Encrypt cannot
# issue for nip.io, and one certificate spanning both would fail
# outright rather than covering the half it can serve.
# The real domain is now the only host, so this covers everything
# served. While nip.io was alongside it, only the real domain could
# appear in tls below — Let's Encrypt cannot issue for nip.io, and one
# certificate spanning both would have failed outright rather than
# covering the half it could serve.
cert-manager.io/cluster-issuer: letsencrypt-prod
path: /
hosts:
- grafana.35.238.248.203.nip.io
- grafana.infra.deployshed.com
tls:
- secretName: grafana-tls
@@ -6,41 +6,69 @@ harbor:
# The homelab runs Harbor on plain HTTP and works around containerd's
# refusal to pull from an insecure registry by hand-editing
# /etc/containerd/certs.d/<host>/hosts.toml on the node. GKE nodes are
# managed and replaced, so that edit cannot survive. Instead the node pool
# is told, at creation, to trust a private CA for exactly this hostname,
# fetching it from Secret Manager (see toolshed-gke-infra's gke-cluster
# module). cert-manager signs Harbor's certificate from that same CA.
# managed and replaced, so that edit cannot survive.
#
# Let's Encrypt is not an option: nip.io is not on the public suffix list,
# so every *.nip.io certificate on the internet shares one rate limit.
# A private CA is fine for image pulls, which is what matters, but
# browsers will warn on the Harbor UI. That is expected, not a fault.
# That used to mean a private CA: the registry was a nip.io name, Let's
# Encrypt cannot issue for one (not on the public suffix list, and every
# *.nip.io certificate shares a single rate limit), so Terraform generated
# a CA, the node pool was told at creation to trust it for exactly that
# hostname, and cert-manager signed Harbor from it. It worked for pulls,
# which is what mattered, but every browser warned on the Harbor UI and
# every docker client that was not a node needed the CA mounted by hand.
#
# Owning a domain removes all of it. harbor.infra.deployshed.com takes an
# ordinary Let's Encrypt certificate that everything already trusts — the
# nodes, dockerd in a build pod, a laptop, a browser. What retires with it:
# registry-ca-clusterissuer.yaml, registry-ca-configmap.yaml, the CA mount
# in every dind pod, and eventually the node pool's own
# private_registry_access_config block in Terraform.
#
# The nip.io name is still served, by a standalone Ingress alongside this
# one, and still signed by the private CA. It has to be: apps deployed
# before the move recorded their image as harbor.35.238.248.203.nip.io/...
# in toolshed's database, and that reference only changes when each app is
# rebuilt. Retiring the old name before then breaks their next image pull.
expose:
type: ingress
tls:
enabled: true
# secret, not the chart's "auto": auto generates its own self-signed
# certificate, which the nodes have no reason to trust. This one is
# signed by the CA they were told to trust.
# certificate, which nothing has any reason to trust.
certSource: secret
secret:
secretName: harbor-tls
secretName: harbor-deployshed-tls
ingress:
hosts:
core: "harbor.35.238.248.203.nip.io"
core: "harbor.infra.deployshed.com"
className: contour
annotations:
# cert-manager's ingress-shim watches for this and creates the
# Certificate itself, writing the result into the secret named
# above. Nothing here ever touches a Certificate resource directly.
# The issuer is defined in devops-infra-argo-config-gcp's
# extra-manifests/registry-ca-clusterissuer.yaml.
cert-manager.io/cluster-issuer: registry-ca-issuer
cert-manager.io/cluster-issuer: letsencrypt-prod
# The update strategy for the two deployments with persistent volumes,
# jobservice and registry. Recreate, not the chart's default RollingUpdate,
# because their volumes are standard-rwo — ReadWriteOnce — and a rolling
# update starts the replacement pod before the old one releases the disk.
# The new pod then waits on "Multi-Attach error for volume ... already used
# by pod" forever, and the rollout never converges: this cluster sat in
# exactly that state, old pods serving while new ones hung in
# ContainerCreating. The chart's own comment on this value says to set it
# this way when RWM volumes are unavailable.
#
# The cost is honest: Harbor is briefly down during an upgrade, because the
# old pod must stop before the new one starts. That beats an update that
# cannot complete at all.
updateStrategy:
type: Recreate
# https, matching the ingress above. Harbor hands this URL to docker
# clients in its own API responses, so a mismatch here breaks pushes in
# ways that look like registry errors rather than configuration.
externalURL: "https://harbor.35.238.248.203.nip.io"
# ways that look like registry errors rather than configuration — and it is
# what the "docker login / docker push" commands shown in Harbor's own UI
# are built from, which is where a stale value is noticed first.
externalURL: "https://harbor.infra.deployshed.com"
# From Vault through External Secrets — see
# secretstores/harbor-admin-credentials.yaml and Vault path
@@ -55,27 +55,23 @@ jenkins:
userKey: jenkins-admin-user
passwordKey: jenkins-admin-password
ingress:
enabled: true
hostName: "jenkins.35.238.248.203.nip.io"
ingressClassName: contour
# One hostname, on the primary ingress, with its certificate.
#
# This chart's primary ingress supports exactly one hostName — no
# extraHosts like argo-cd. secondaryingress renders a whole second
# Ingress object at the same backend, which is the supported way to get
# a second hostname here.
# extraHosts like argo-cd — so while nip.io was also served, the real
# domain lived in a whole second Ingress object (secondaryingress). That
# was the only way to keep the certificate clean, since a certificate
# covering both names is impossible: Let's Encrypt cannot issue for
# nip.io. With nip.io gone there is one name, so the second object is
# gone with it and the certificate moves onto the primary.
#
# paths must be set explicitly: the template renders zero routes if left
# at the chart's own default of [], unlike the primary ingress, and the
# hostname would then answer nothing at all.
#
# Being a separate Ingress object is what makes the certificate clean —
# it covers this hostname alone, with no nip.io name to drag in.
secondaryingress:
# controller.ingress.tls is a LIST here, not a boolean, taking an
# explicit secretName — so jenkins-tls, already issued for this exact
# hostname by the secondary ingress, is adopted rather than reissued.
ingress:
enabled: true
hostName: "jenkins.infra.deployshed.com"
ingressClassName: contour
paths:
- /
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
@@ -84,11 +84,10 @@ vault:
enabled: true
ingressClassName: contour
annotations:
# Certificate for the real domain only; nip.io cannot have one.
# Certificate for the real domain, which is now the only one served.
# nip.io could never have had one.
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: "vault.35.238.248.203.nip.io"
paths: []
- host: "vault.infra.deployshed.com"
paths: []
tls:
@@ -56,12 +56,10 @@ victoria-metrics-single:
enabled: true
ingressClassName: contour
annotations:
# Certificate for the real domain only; nip.io cannot have one.
# Certificate for the real domain, now the only one served. nip.io
# could never have had one.
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- name: vm.35.238.248.203.nip.io
path: ["/"]
port: http
- name: vm.infra.deployshed.com
path: ["/"]
port: http