feat(manifest): network_aliases — extra DNS aliases on a container's network

Add `container.network_aliases: Vec<String>` (serde default, DNS-label
validated) so a stack member can answer to short hostnames its peers bake
in, beyond its own container name. Rendered in both runtime paths:
- podman_client: merged (deduped) into the custom-network aliases array.
- quadlet from_manifest: appended after the container name; emitted only
  for Bridge networks (slirp/pasta reject aliases).

Needed for the indeedhub migration: its frontend nginx proxies to
`api:4000` / `minio:9000` / `relay:8080`, so those members declare
`network_aliases: [api|minio|relay]` to keep the short names resolvable on
the dedicated indeedhub-net (vs. colliding generic aliases on archy-net).

Also fixes 4 pre-existing from_manifest test failures (unrelated to this
change, surfaced now that the quadlet suite runs green): test manifests
used the long-invalid `network_policy: archy-net` (allowlist is
isolated/bridge/host → moved to network_policy: isolated + container.network)
and bind sources outside /var/lib/archipelago.

Tests: container crate 53 pass; archipelago quadlet+alias 47 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-21 15:45:11 -04:00
co-authored by Claude Opus 4.8
parent ccb5b7ca39
commit b94b61f640
3 changed files with 88 additions and 7 deletions
+44 -6
View File
@@ -410,7 +410,18 @@ impl QuadletUnit {
environment: app.environment.clone(),
devices: app.devices.clone(),
add_hosts: vec![("host.archipelago".into(), "10.89.0.1".into())],
network_aliases: vec![name.to_string()],
// Container always answers to its own name; manifest extras add the
// short hostnames peers bake in (e.g. indeedhub api/minio/relay).
// Only emitted for Bridge networks (slirp/pasta reject aliases).
network_aliases: {
let mut a = vec![name.to_string()];
for extra in &app.container.network_aliases {
if !a.iter().any(|x| x == extra) {
a.push(extra.clone());
}
}
a
},
entrypoint: app.container.entrypoint.clone(),
command: app.container.custom_args.clone(),
read_only_root: app.security.readonly_root,
@@ -1060,6 +1071,7 @@ app:
version: 1.0.0
container:
image: registry/bitcoin-knots:1.0
network: archy-net
entrypoint: ["/usr/local/bin/bitcoind"]
custom_args: ["-server=1", "-rpcbind=0.0.0.0"]
ports:
@@ -1080,7 +1092,7 @@ app:
security:
capabilities: ["NET_BIND_SERVICE"]
readonly_root: true
network_policy: archy-net
network_policy: isolated
"#;
let m = AppManifest::parse(yaml).expect("manifest must parse");
let u = QuadletUnit::from_manifest(&m, "bitcoin-knots");
@@ -1220,7 +1232,7 @@ app:
image: x:latest
volumes:
- type: bind
source: /etc/host-conf
source: /var/lib/archipelago/x-conf
target: /etc/conf
options: ["ro"]
"#;
@@ -1244,7 +1256,7 @@ app:
target: /tmp
tmpfs_options: "rw,size=64m"
- type: bind
source: /var/lib/x
source: /var/lib/archipelago/x
target: /data
options: []
"#;
@@ -1252,7 +1264,7 @@ app:
let u = QuadletUnit::from_manifest(&m, "x");
// tmpfs entry is dropped from bind_mounts; bind entry survives.
assert_eq!(u.bind_mounts.len(), 1);
assert_eq!(u.bind_mounts[0].host, PathBuf::from("/var/lib/x"));
assert_eq!(u.bind_mounts[0].host, PathBuf::from("/var/lib/archipelago/x"));
}
#[test]
@@ -1431,6 +1443,31 @@ app:
assert!(!publish_ports_changed(new, new));
}
#[test]
fn from_manifest_appends_manifest_network_aliases_for_bridge() {
let yaml = r#"
app:
id: indeedhub-api
name: IndeedHub API
version: 1.0.0
container:
image: registry/indeedhub-api:1.0.0
network: indeedhub-net
network_aliases: [api]
security:
capabilities: []
network_policy: isolated
"#;
let m = AppManifest::parse(yaml).expect("manifest must parse");
let u = QuadletUnit::from_manifest(&m, "indeedhub-api");
assert!(matches!(u.network, NetworkMode::Bridge(ref n) if n == "indeedhub-net"));
// Own name first, then the baked-in short alias the frontend nginx uses.
assert_eq!(u.network_aliases, vec!["indeedhub-api", "api"]);
let s = u.render();
assert!(s.contains("NetworkAlias=api"));
assert!(s.contains("PodmanArgs=--network-alias=api"));
}
#[test]
fn network_aliases_changed_detects_service_discovery_drift() {
let old = "[Container]\nNetwork=archy-net\n";
@@ -1489,6 +1526,7 @@ app:
version: 1.0.0
container:
image: registry/lnd:latest
network: archy-net
ports:
- host: 10009
container: 10009
@@ -1504,7 +1542,7 @@ app:
memory_limit: 1g
security:
capabilities: []
network_policy: archy-net
network_policy: isolated
"#;
let m = AppManifest::parse(yaml).unwrap();
let body = QuadletUnit::from_manifest(&m, "lnd").render();