Skip to content

Commit 18143bb

Browse files
authored
fix(hosted): name the ingress network on routed runtime services (#30)
Coolify puts every routed container on two networks: the application network that carries the ingress proxy, and the private runtime network its peers address. Traefik reads a container's address from the first network it iterates when no network is named, and Go randomises that order, so about half of the routed services were resolved to their 172.29.x runtime address after every proxy configuration rebuild and answered nothing on their public route. Restarting one container reshuffled the choice for all of them. Name the network explicitly on the twelve routed services that join both. Coolify exports the application uuid into the environment it runs Compose in, and names the proxy-carrying network after it, so the label resolves without a new operator-set variable and fails the deployment loudly if it is ever absent. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
1 parent fc0f9b1 commit 18143bb

9 files changed

Lines changed: 56 additions & 12 deletions

‎compose.coolify.agriculture.yaml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ services:
3131
command: [serve, --runtime, /etc/relay/nagdi/runtime.yaml]
3232
environment: {SOLMARA_RELAY_AUDIT_KEY: "${NAGDI_RELAY_AUDIT_KEY:?required}", SOLMARA_RELAY_CURSOR_KEY: "${NAGDI_RELAY_CURSOR_KEY:?required}"}
3333
volumes: [/data/solmara-authority-cells/nagdi-relay/runtime:/etc/relay/nagdi:ro, /data/solmara-authority-cells/nagdi-relay/source:/var/lib/relay/source:ro, nagdi-relay-audit:/var/lib/relay/audit]
34-
labels: {solmara.lab.host: nagdi-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells}
34+
labels: {solmara.lab.host: nagdi-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
3535

3636
nagdi-evidence:
3737
<<: *runtime
@@ -42,7 +42,7 @@ services:
4242
depends_on:
4343
audit-permissions: {condition: service_completed_successfully}
4444
nagdi-relay: {condition: service_started}
45-
labels: {solmara.lab.host: "${SOLMARA_NAGDI_EVIDENCE_PUBLIC_HOST:-nagdi-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells}
45+
labels: {solmara.lab.host: "${SOLMARA_NAGDI_EVIDENCE_PUBLIC_HOST:-nagdi-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
4646

4747
volumes:
4848
nagdi-relay-audit:

‎compose.coolify.interior.yaml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ services:
3131
command: [serve, --runtime, /etc/relay/cra/runtime.yaml]
3232
environment: {SOLMARA_RELAY_AUDIT_KEY: "${CRA_RELAY_AUDIT_KEY:?required}"}
3333
volumes: [/data/solmara-authority-cells/cra-relay/runtime:/etc/relay/cra:ro, /data/solmara-authority-cells/cra-relay/source:/var/lib/relay/source:ro, cra-relay-audit:/var/lib/relay/audit]
34-
labels: {solmara.lab.host: cra-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells}
34+
labels: {solmara.lab.host: cra-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
3535

3636
nia-relay:
3737
<<: *runtime
3838
image: ${REGISTRY_RELAY_IMAGE:?set the digest-pinned official Registry Stack Relay image}
3939
command: [serve, --runtime, /etc/relay/nia/runtime.yaml]
4040
environment: {SOLMARA_RELAY_AUDIT_KEY: "${NIA_RELAY_AUDIT_KEY:?required}"}
4141
volumes: [/data/solmara-authority-cells/nia-relay/runtime:/etc/relay/nia:ro, /data/solmara-authority-cells/nia-relay/source:/var/lib/relay/source:ro, nia-relay-audit:/var/lib/relay/audit]
42-
labels: {solmara.lab.host: nia-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells}
42+
labels: {solmara.lab.host: nia-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
4343

4444
cra-evidence:
4545
<<: *runtime
@@ -50,15 +50,15 @@ services:
5050
depends_on:
5151
audit-permissions: {condition: service_completed_successfully}
5252
cra-relay: {condition: service_started}
53-
labels: {solmara.lab.host: "${SOLMARA_CRA_EVIDENCE_PUBLIC_HOST:-cra-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells}
53+
labels: {solmara.lab.host: "${SOLMARA_CRA_EVIDENCE_PUBLIC_HOST:-cra-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
5454

5555
nia-evidence:
5656
<<: *runtime
5757
image: ${SOLMARA_EVIDENCE_IMAGE:?set the digest-pinned official Registry Stack Evidence image}
5858
command: [--runtime, /etc/registry-evidence/nia/runtime.yaml, serve]
5959
volumes: [/data/solmara-authority-cells/nia-evidence/runtime:/etc/registry-evidence/nia:ro, /data/solmara-authority-cells/nia-evidence/secrets:/run/secrets/registry-evidence/nia:ro, /data/solmara-authority-cells/nia-evidence/transit:/run/registry-evidence/nia:ro, /data/solmara-authority-cells/nia-evidence/extracts:/var/lib/registry-evidence/nia/extracts:ro, nia-evidence-audit:/var/lib/registry-evidence/nia/audit]
6060
networks: {runtime: {ipv4_address: 172.29.2.22}}
61-
labels: {solmara.lab.host: "${SOLMARA_NIA_EVIDENCE_PUBLIC_HOST:-nia-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells}
61+
labels: {solmara.lab.host: "${SOLMARA_NIA_EVIDENCE_PUBLIC_HOST:-nia-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
6262

6363
volumes:
6464
cra-relay-audit:

‎compose.coolify.labour-pensions.yaml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ services:
3131
command: [serve, --runtime, /etc/relay/sipf/runtime.yaml]
3232
environment: {SOLMARA_RELAY_AUDIT_KEY: "${SIPF_RELAY_AUDIT_KEY:?required}", SOLMARA_RELAY_CURSOR_KEY: "${SIPF_RELAY_CURSOR_KEY:?required}"}
3333
volumes: [/data/solmara-authority-cells/sipf-relay/runtime:/etc/relay/sipf:ro, /data/solmara-authority-cells/sipf-relay/source:/var/lib/relay/source:ro, sipf-relay-audit:/var/lib/relay/audit]
34-
labels: {solmara.lab.host: sipf-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells}
34+
labels: {solmara.lab.host: sipf-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
3535

3636
sipf-evidence:
3737
<<: *runtime
@@ -42,7 +42,7 @@ services:
4242
depends_on:
4343
audit-permissions: {condition: service_completed_successfully}
4444
sipf-relay: {condition: service_started}
45-
labels: {solmara.lab.host: "${SOLMARA_SIPF_EVIDENCE_PUBLIC_HOST:-sipf-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells}
45+
labels: {solmara.lab.host: "${SOLMARA_SIPF_EVIDENCE_PUBLIC_HOST:-sipf-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
4646

4747
volumes:
4848
sipf-relay-audit:

‎compose.coolify.social-development.yaml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ services:
3131
command: [serve, --runtime, /etc/relay/mosd/runtime.yaml]
3232
environment: {SOLMARA_RELAY_AUDIT_KEY: "${MOSD_RELAY_AUDIT_KEY:?required}"}
3333
volumes: [/data/solmara-authority-cells/mosd-relay/runtime:/etc/relay/mosd:ro, /data/solmara-authority-cells/mosd-relay/source:/var/lib/relay/source:ro, mosd-relay-audit:/var/lib/relay/audit]
34-
labels: {solmara.lab.host: mosd-programme-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells}
34+
labels: {solmara.lab.host: mosd-programme-relay-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
3535

3636
sro-evidence:
3737
<<: *runtime
3838
image: ${SOLMARA_EVIDENCE_IMAGE:?set the digest-pinned official Registry Stack Evidence image}
3939
command: [--runtime, /etc/registry-evidence/sro/runtime.yaml, serve]
4040
volumes: [/data/solmara-authority-cells/sro-evidence/runtime:/etc/registry-evidence/sro:ro, /data/solmara-authority-cells/sro-evidence/secrets:/run/secrets/registry-evidence/sro:ro, /data/solmara-authority-cells/sro-evidence/transit:/run/registry-evidence/sro:ro, /data/solmara-authority-cells/sro-evidence/extracts:/var/lib/registry-evidence/sro/extracts:ro, sro-evidence-audit:/var/lib/registry-evidence/sro/audit]
4141
networks: {runtime: {ipv4_address: 172.29.3.23}}
42-
labels: {solmara.lab.host: "${SOLMARA_SRO_EVIDENCE_PUBLIC_HOST:-sro-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells}
42+
labels: {solmara.lab.host: "${SOLMARA_SRO_EVIDENCE_PUBLIC_HOST:-sro-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
4343

4444
mosd-programme-evidence:
4545
<<: *runtime
@@ -50,7 +50,7 @@ services:
5050
depends_on:
5151
audit-permissions: {condition: service_completed_successfully}
5252
mosd-relay: {condition: service_started}
53-
labels: {solmara.lab.host: "${SOLMARA_MOSD_PROGRAMME_EVIDENCE_PUBLIC_HOST:-mosd-programme-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells}
53+
labels: {solmara.lab.host: "${SOLMARA_MOSD_PROGRAMME_EVIDENCE_PUBLIC_HOST:-mosd-programme-evidence.solmara.registrystack.org}", solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
5454

5555
volumes:
5656
mosd-relay-audit:

‎compose.coolify.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ services:
3636
networks: {runtime: {ipv4_address: 172.29.1.20}}
3737
depends_on:
3838
audit-permissions: {condition: service_completed_successfully}
39-
labels: {solmara.lab.host: mint-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells}
39+
labels: {solmara.lab.host: mint-authority-cells.solmara.registrystack.org, solmara.rollout.slot: authority-cells, traefik.docker.network: "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"}
4040
mint-readiness:
4141
image: ${SOLMARA_AUTHORITY_PROVISIONER_IMAGE:?set the digest-pinned Solmara authority provisioner image}
4242
entrypoint: [python, -c]

‎docs/hosted-deployment.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ runtime requires before it accepts a wildcard bind. Isolation rests on network
7575
membership rather than on the bind address: an application's networks carry only
7676
its own containers and the proxy, so no authority reaches another's cells.
7777

78+
Joining two networks makes a container's routable address ambiguous. Traefik
79+
reads the address from the first network it iterates when no network is named,
80+
and that order is randomised, so an unnamed service is routed to its private
81+
runtime address on roughly half of every proxy configuration rebuild and then
82+
answers nothing on its public route. Every routed service that joins the runtime
83+
network therefore carries
84+
`traefik.docker.network: ${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}`,
85+
which resolves to the Coolify-managed network the proxy is on. A routed service
86+
that joins the ingress network alone has no choice to remove and carries no such
87+
label.
88+
7889
`compose.coolify.provision.yaml` is a dedicated operator-only application. It
7990
runs only the one-shot target provisioners and is the sole writer of every
8091
shared path except the seven Transit sockets.

‎scripts/check-coolify-compose.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ scratch=$(mktemp -d)
1010
trap 'rm -rf -- "$scratch"' EXIT HUP INT TERM
1111

1212
check_compose() {
13+
COOLIFY_RESOURCE_UUID=solmara-test-application \
1314
REGISTRY_RELAY_IMAGE="ghcr.io/registrystack/relay@sha256:$test_digest" \
1415
SOLMARA_EVIDENCE_IMAGE="$test_image" \
1516
SOLMARA_MINT_IMAGE="$test_image" \

‎scripts/test_hosted_evidence_routes.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272

7373
def compose_environment() -> dict[str, str]:
7474
environment = os.environ | {
75+
# Coolify exports this into the environment it runs Compose in, and the
76+
# routed services read it to name the network the ingress proxy is on.
77+
"COOLIFY_RESOURCE_UUID": "test-application-uuid",
7578
"SOLMARA_AUTHORITY_PROVISIONER_IMAGE": TEST_IMAGE,
7679
"REGISTRY_RELAY_IMAGE": TEST_IMAGE,
7780
"SOLMARA_EVIDENCE_IMAGE": TEST_IMAGE,

‎scripts/test_hosted_network_isolation.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
# application's networks contain another application's containers, so binding
3131
# every interface reaches only the owning application and the proxy.
3232
BIND_HOST = "0.0.0.0"
33+
# Coolify exports the application uuid into the environment it runs Compose in,
34+
# and names the network that carries the ingress proxy after it.
35+
INGRESS_NETWORK = "${COOLIFY_RESOURCE_UUID:?set by Coolify for every deployment}"
3336
PROVISIONED_SERVICES = {
3437
"mint-provisioner": ("core", "mint", "172.29.1.20"),
3538
"cra-evidence-provisioner": ("interior", "cra-evidence", "172.29.2.21"),
@@ -107,6 +110,32 @@ def test_provisioned_services_keep_distinct_addresses_in_their_subnet(self) -> N
107110
)
108111
addresses[expected_address] = service_name
109112

113+
def test_routed_services_on_the_runtime_network_name_the_ingress_network(
114+
self,
115+
) -> None:
116+
"""Traefik reads a container's address from the first network it iterates
117+
when no network is named, and that order is randomised, so a service that
118+
joins both the runtime network and the ingress network is routed to its
119+
private runtime address on roughly half of every proxy configuration
120+
rebuild and then answers nothing. Naming the ingress network removes the
121+
choice. A service that joins the ingress network alone has no choice to
122+
remove, so it carries no label."""
123+
pinned = 0
124+
for app, compose in self.runtime.items():
125+
for service_name, service in compose["services"].items():
126+
labels = service.get("labels") or {}
127+
if "solmara.lab.host" not in labels:
128+
continue
129+
where = f"{app}/{service_name}"
130+
if "runtime" not in (service.get("networks") or {}):
131+
self.assertNotIn("traefik.docker.network", labels, where)
132+
continue
133+
self.assertEqual(
134+
labels.get("traefik.docker.network"), INGRESS_NETWORK, where
135+
)
136+
pinned += 1
137+
self.assertEqual(pinned, 12)
138+
110139
def test_provisioning_services_remain_networkless(self) -> None:
111140
self.assertNotIn("networks", self.provision)
112141
for service_name, service in self.provision["services"].items():

0 commit comments

Comments
 (0)