A deploy today produces exactly one long-lived container per app, and every
label the app defines — including any traefik.* routing labels — is hardcoded
onto that one container. There is no way to place a second container beside the
app and move the ingress labels onto it, which is what the smallwebwaf sidecar
WAF requires (proxy in front carrying the traefik labels, app off the ingress
network). Current code:
One container per deploy: internal/service/deploy/deploy.go:565
(deployContainerWithTimeout) removes the single old container
(removeOldContainer, deploy.go:1034) then calls createAndStartContainer
(deploy.go:1061) once, which calls CreateContainer + StartContainer a
single time. Container name is "upaas-" + app.Name (deploy.go:1156).
One label set on that one container: buildContainerOptions
(deploy.go:1108) / buildLabelMap (deploy.go:1168) attach all app labels
plus upaas.id to the sole container; CreateContainer sets them at internal/docker/client.go:258.
Single-container assumption in lookup/replace: FindContainerByAppID returns containers[0] (client.go:431).
Minimal feature
Allow an app to optionally declare one extra "sidecar" container and control
which container receives the ingress labels. High level:
Data model (internal/models/app.go plus a new child table, mirroring the
existing env/label/volume/port tables): store an optional sidecar spec (image
reference, its own env/ports, internal network name) and a marker for which
labels are "ingress" labels that belong on the front container.
Deploy orchestration (deploy.go:565deployContainerWithTimeout, deploy.go:1061createAndStartContainer): create/start the set of
containers (app + sidecar) instead of one; put the app on an internal-only
network and the sidecar on both the ingress network and the internal network
so it can forward to the app (app already has a single docker_network, app.go:48).
Label routing (deploy.go:1108 / :1168): send the ingress/traefik labels
to the sidecar and keep upaas.id on every container in the set.
Multi-container lifecycle (client.go:431FindContainerByAppID, deploy.go:1034removeOldContainer): find and remove all containers for an
app id (they already share the upaas.id label; add a role label to
disambiguate) rather than assuming exactly one.
Note this crosses the current README "Non-Goals: Multiple container
orchestration"; treat that line as an owner decision to reconcile.
Definition of done
An app can be configured to deploy with a sidecar container, and the app's
ingress (traefik) labels are applied to the sidecar, not the app container.
On redeploy and rollback, the whole container set for the app is replaced
cleanly.
Docs updated to describe the sidecar option and reconcile the Non-Goals line.
Model: opus-4-8
Split from https://git.eeqj.de/sneak/upaas/issues/205.
## Problem
A deploy today produces exactly one long-lived container per app, and every
label the app defines — including any `traefik.*` routing labels — is hardcoded
onto that one container. There is no way to place a second container beside the
app and move the ingress labels onto it, which is what the smallwebwaf sidecar
WAF requires (proxy in front carrying the traefik labels, app off the ingress
network). Current code:
- One container per deploy: `internal/service/deploy/deploy.go:565`
(`deployContainerWithTimeout`) removes the single old container
(`removeOldContainer`, `deploy.go:1034`) then calls `createAndStartContainer`
(`deploy.go:1061`) once, which calls `CreateContainer` + `StartContainer` a
single time. Container name is `"upaas-" + app.Name` (`deploy.go:1156`).
- One label set on that one container: `buildContainerOptions`
(`deploy.go:1108`) / `buildLabelMap` (`deploy.go:1168`) attach all app labels
plus `upaas.id` to the sole container; `CreateContainer` sets them at
`internal/docker/client.go:258`.
- Single-container assumption in lookup/replace: `FindContainerByAppID` returns
`containers[0]` (`client.go:431`).
## Minimal feature
Allow an app to optionally declare one extra "sidecar" container and control
which container receives the ingress labels. High level:
1. Data model (`internal/models/app.go` plus a new child table, mirroring the
existing env/label/volume/port tables): store an optional sidecar spec (image
reference, its own env/ports, internal network name) and a marker for which
labels are "ingress" labels that belong on the front container.
2. Deploy orchestration (`deploy.go:565` `deployContainerWithTimeout`,
`deploy.go:1061` `createAndStartContainer`): create/start the set of
containers (app + sidecar) instead of one; put the app on an internal-only
network and the sidecar on both the ingress network and the internal network
so it can forward to the app (app already has a single `docker_network`,
`app.go:48`).
3. Label routing (`deploy.go:1108` / `:1168`): send the ingress/traefik labels
to the sidecar and keep `upaas.id` on every container in the set.
4. Multi-container lifecycle (`client.go:431` `FindContainerByAppID`,
`deploy.go:1034` `removeOldContainer`): find and remove all containers for an
app id (they already share the `upaas.id` label; add a role label to
disambiguate) rather than assuming exactly one.
Note this crosses the current README "Non-Goals: Multiple container
orchestration"; treat that line as an owner decision to reconcile.
## Definition of done
- An app can be configured to deploy with a sidecar container, and the app's
ingress (traefik) labels are applied to the sidecar, not the app container.
- On redeploy and rollback, the whole container set for the app is replaced
cleanly.
- Docs updated to describe the sidecar option and reconcile the Non-Goals line.
Model: opus-4-8
let's not put this functionality into upaas. we can simply decline to put the traefik labels on the upaas-deployed app and docker-compose can put them onto the sidecar, and the sidecar can point at the app's hostname. upaas doesn't need to know about the sidecar.
let's not put this functionality into upaas. we can simply decline to put the traefik labels on the upaas-deployed app and docker-compose can put them onto the sidecar, and the sidecar can point at the app's hostname. upaas doesn't need to know about the sidecar.
Ruling (sneak, 2026-09-22, above): feature REJECTED — upaas stays single-container and never learns about sidecars. The deployment shape instead: the app is deployed by upaas with no traefik labels configured; the sidecar runs beside it under docker-compose, carries the traefik labels, and points at the app's hostname. No upaas change is needed for that shape. Closing as declined; consequence recorded for smallwebwaf on sneak/smallwebwaf#6. (model: claude-fable-5)
Ruling (sneak, 2026-09-22, above): feature REJECTED — upaas stays single-container and never learns about sidecars. The deployment shape instead: the app is deployed by upaas with no traefik labels configured; the sidecar runs beside it under docker-compose, carries the traefik labels, and points at the app's hostname. No upaas change is needed for that shape. Closing as declined; consequence recorded for smallwebwaf on https://git.eeqj.de/sneak/smallwebwaf/issues/6. (model: claude-fable-5)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Split from #205.
Problem
A deploy today produces exactly one long-lived container per app, and every
label the app defines — including any
traefik.*routing labels — is hardcodedonto that one container. There is no way to place a second container beside the
app and move the ingress labels onto it, which is what the smallwebwaf sidecar
WAF requires (proxy in front carrying the traefik labels, app off the ingress
network). Current code:
internal/service/deploy/deploy.go:565(
deployContainerWithTimeout) removes the single old container(
removeOldContainer,deploy.go:1034) then callscreateAndStartContainer(
deploy.go:1061) once, which callsCreateContainer+StartContainerasingle time. Container name is
"upaas-" + app.Name(deploy.go:1156).buildContainerOptions(
deploy.go:1108) /buildLabelMap(deploy.go:1168) attach all app labelsplus
upaas.idto the sole container;CreateContainersets them atinternal/docker/client.go:258.FindContainerByAppIDreturnscontainers[0](client.go:431).Minimal feature
Allow an app to optionally declare one extra "sidecar" container and control
which container receives the ingress labels. High level:
internal/models/app.goplus a new child table, mirroring theexisting env/label/volume/port tables): store an optional sidecar spec (image
reference, its own env/ports, internal network name) and a marker for which
labels are "ingress" labels that belong on the front container.
deploy.go:565deployContainerWithTimeout,deploy.go:1061createAndStartContainer): create/start the set ofcontainers (app + sidecar) instead of one; put the app on an internal-only
network and the sidecar on both the ingress network and the internal network
so it can forward to the app (app already has a single
docker_network,app.go:48).deploy.go:1108/:1168): send the ingress/traefik labelsto the sidecar and keep
upaas.idon every container in the set.client.go:431FindContainerByAppID,deploy.go:1034removeOldContainer): find and remove all containers for anapp id (they already share the
upaas.idlabel; add a role label todisambiguate) rather than assuming exactly one.
Note this crosses the current README "Non-Goals: Multiple container
orchestration"; treat that line as an owner decision to reconcile.
Definition of done
ingress (traefik) labels are applied to the sidecar, not the app container.
cleanly.
Model: opus-4-8
let's not put this functionality into upaas. we can simply decline to put the traefik labels on the upaas-deployed app and docker-compose can put them onto the sidecar, and the sidecar can point at the app's hostname. upaas doesn't need to know about the sidecar.
Ruling (sneak, 2026-09-22, above): feature REJECTED — upaas stays single-container and never learns about sidecars. The deployment shape instead: the app is deployed by upaas with no traefik labels configured; the sidecar runs beside it under docker-compose, carries the traefik labels, and points at the app's hostname. No upaas change is needed for that shape. Closing as declined; consequence recorded for smallwebwaf on sneak/smallwebwaf#6. (model: claude-fable-5)