docs(deployment): describe per-pass verification and stop/remove of old containers
- commit
- a2e0a06b7151d3a44a0d88aadd24e83981c68762
- author
- brice · · 2026-10-16 19:56 UTC
- parent
- 99672d0
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +3 -3
M docs/concepts.md +1 -1
| @@ -93,7 +93,7 @@ If replacement fails after the old container was removed, the failure is explici | |||
| 93 | 93 | ||
| 94 | 94 | Deployment stops at the first service failure: services already deployed in that run are kept and are not rolled back. This covers TCP, UDP, mixed, and volume-owning services. An open UDP socket is not application readiness, and Gordon never restarts an old volume-owning image automatically after a replacement may have written data. | |
| 95 | 95 | ||
| 96 | - | A deploy interrupted after the replacement container was created but before it was published leaves the failure explicit: the candidate is recorded in the deployment journal, and Gordon removes it before it creates or rebuilds any generation of that app — at boot and before every mutation. For a stateless service the old container keeps serving and is kept. A deploy interrupted after the switch but before the old container was stopped leaves two containers running; the retirement mark makes the periodic recovery pass stop the old one after it has verified and routed the published container. Boot recovery never routes a persisted port it has not verified. An interrupted operation is finalized instead of staying in flight: as a failure, or as the success it was when every step had already completed. If a recorded candidate cannot be removed, reconciliation fails closed: the operation is not finalized, stays the latest journal, and every later mutation is refused so a newer operation can never mask the orphan. The next recovery pass republishes routing within its 15-second cadence if the final traffic publication was the step that failed. | |
| 96 | + | A deploy interrupted after the replacement container was created but before it was published leaves the failure explicit: the candidate is recorded in the deployment journal, and Gordon removes it before it creates or rebuilds any generation of that app — at boot and before every mutation. For a stateless service the old container keeps serving and is kept. A deploy interrupted after the switch but before the old container was stopped leaves two containers running; the retirement mark makes the periodic recovery pass stop the old one, but only once that same pass has verified and routed the published container. `gordon apps stop` and `gordon apps remove` also stop any such old container, and fail, keeping the mark, if they cannot. Boot recovery never routes a persisted port it has not verified. An interrupted operation is finalized instead of staying in flight: as a failure, or as the success it was when every step had already completed. If a recorded candidate cannot be removed, reconciliation fails closed: the operation is not finalized, stays the latest journal, and every later mutation is refused so a newer operation can never mask the orphan. The next recovery pass republishes routing within its 15-second cadence if the final traffic publication was the step that failed. | |
| 97 | 97 | ||
| 98 | 98 | Deploy is the only command that applies changes: it recreates a service whose image digest, spec, app environment, or secret values changed, and keeps an unchanged one, reported `unchanged`. | |
| 99 | 99 | ||
| @@ -93,7 +93,7 @@ If replacement fails after the old container was removed, the failure is explici | |||||
| 93 | 93 | ||||
| 94 | Deployment stops at the first service failure: services already deployed in that run are kept and are not rolled back. This covers TCP, UDP, mixed, and volume-owning services. An open UDP socket is not application readiness, and Gordon never restarts an old volume-owning image automatically after a replacement may have written data. | 94 | Deployment stops at the first service failure: services already deployed in that run are kept and are not rolled back. This covers TCP, UDP, mixed, and volume-owning services. An open UDP socket is not application readiness, and Gordon never restarts an old volume-owning image automatically after a replacement may have written data. | ||
| 95 | 95 | ||||
| 96 | - | A deploy interrupted after the replacement container was created but before it was published leaves the failure explicit: the candidate is recorded in the deployment journal, and Gordon removes it before it creates or rebuilds any generation of that app — at boot and before every mutation. For a stateless service the old container keeps serving and is kept. A deploy interrupted after the switch but before the old container was stopped leaves two containers running; the retirement mark makes the periodic recovery pass stop the old one after it has verified and routed the published container. Boot recovery never routes a persisted port it has not verified. An interrupted operation is finalized instead of staying in flight: as a failure, or as the success it was when every step had already completed. If a recorded candidate cannot be removed, reconciliation fails closed: the operation is not finalized, stays the latest journal, and every later mutation is refused so a newer operation can never mask the orphan. The next recovery pass republishes routing within its 15-second cadence if the final traffic publication was the step that failed. | 96 | + | A deploy interrupted after the replacement container was created but before it was published leaves the failure explicit: the candidate is recorded in the deployment journal, and Gordon removes it before it creates or rebuilds any generation of that app — at boot and before every mutation. For a stateless service the old container keeps serving and is kept. A deploy interrupted after the switch but before the old container was stopped leaves two containers running; the retirement mark makes the periodic recovery pass stop the old one, but only once that same pass has verified and routed the published container. `gordon apps stop` and `gordon apps remove` also stop any such old container, and fail, keeping the mark, if they cannot. Boot recovery never routes a persisted port it has not verified. An interrupted operation is finalized instead of staying in flight: as a failure, or as the success it was when every step had already completed. If a recorded candidate cannot be removed, reconciliation fails closed: the operation is not finalized, stays the latest journal, and every later mutation is refused so a newer operation can never mask the orphan. The next recovery pass republishes routing within its 15-second cadence if the final traffic publication was the step that failed. |
| 97 | 97 | ||||
| 98 | Deploy is the only command that applies changes: it recreates a service whose image digest, spec, app environment, or secret values changed, and keeps an unchanged one, reported `unchanged`. | 98 | Deploy is the only command that applies changes: it recreates a service whose image digest, spec, app environment, or secret values changed, and keeps an unchanged one, reported `unchanged`. | ||
| 99 | 99 | ||||
M docs/config/apps.md +1 -1
| @@ -116,7 +116,7 @@ A deploy recreates a service whose image digest, spec, environment, or secret va | |||
| 116 | 116 | ||
| 117 | 117 | - A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime. The new container starts and passes its readiness probe while the old one keeps serving; traffic then switches to the new container; only afterwards does the old container receive SIGTERM, and it is killed once its `stop_grace` (default `30s`) runs out. If the new container is not ready, it is removed and the old one is left untouched. | |
| 118 | 118 | - During the overlap both containers answer to the same network alias, so other services of the app may reach either generation for a short time. Database migrations must be backward compatible with the previous version of the service. | |
| 119 | - | - A service with a volume, bind, device, or UDP interface is replaced sequentially: traffic is withdrawn, the old container is stopped and removed, then the new one starts. It may be briefly interrupted, and two generations of it never run at once. UDP services are excluded from the overlap because UDP sessions stay pinned to the backend they started on. | |
| 119 | + | - A service with a volume, bind, device, or UDP interface, or whose currently running generation has one, is replaced sequentially: traffic is withdrawn, the old container is stopped and removed, then the new one starts. It may be briefly interrupted, and two generations of it never run at once. UDP services are excluded from the overlap because UDP sessions stay pinned to the backend they started on. | |
| 120 | 120 | ||
| 121 | 121 | ## Environment and Secrets | |
| 122 | 122 | ||
| @@ -116,7 +116,7 @@ A deploy recreates a service whose image digest, spec, environment, or secret va | |||||
| 116 | 116 | ||||
| 117 | - A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime. The new container starts and passes its readiness probe while the old one keeps serving; traffic then switches to the new container; only afterwards does the old container receive SIGTERM, and it is killed once its `stop_grace` (default `30s`) runs out. If the new container is not ready, it is removed and the old one is left untouched. | 117 | - A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime. The new container starts and passes its readiness probe while the old one keeps serving; traffic then switches to the new container; only afterwards does the old container receive SIGTERM, and it is killed once its `stop_grace` (default `30s`) runs out. If the new container is not ready, it is removed and the old one is left untouched. | ||
| 118 | - During the overlap both containers answer to the same network alias, so other services of the app may reach either generation for a short time. Database migrations must be backward compatible with the previous version of the service. | 118 | - During the overlap both containers answer to the same network alias, so other services of the app may reach either generation for a short time. Database migrations must be backward compatible with the previous version of the service. | ||
| 119 | - | - A service with a volume, bind, device, or UDP interface is replaced sequentially: traffic is withdrawn, the old container is stopped and removed, then the new one starts. It may be briefly interrupted, and two generations of it never run at once. UDP services are excluded from the overlap because UDP sessions stay pinned to the backend they started on. | 119 | + | - A service with a volume, bind, device, or UDP interface, or whose currently running generation has one, is replaced sequentially: traffic is withdrawn, the old container is stopped and removed, then the new one starts. It may be briefly interrupted, and two generations of it never run at once. UDP services are excluded from the overlap because UDP sessions stay pinned to the backend they started on. |
| 120 | 120 | ||||
| 121 | ## Environment and Secrets | 121 | ## Environment and Secrets | ||
| 122 | 122 | ||||
M docs/deployment/index.md +1 -1
| @@ -162,7 +162,7 @@ Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─ | |||
| 162 | 162 | ||
| 163 | 163 | Preflight (image resolution and pull, secrets, volumes, networks, bind policy, reservations) completes before anything is disrupted, so a preflight failure leaves the running service untouched. | |
| 164 | 164 | ||
| 165 | - | For a stateless service the old container serves until the new one is ready and routed, then receives SIGTERM and is killed after its `stop_grace` (default 30s). During the overlap both containers share the service's network alias, so other services of the app may reach either one. Database migrations must stay backward compatible with the previous version of the service. A new container that fails readiness is removed and the old one is not touched. An old container that cannot be removed is reported as a cleanup warning without failing the deploy; it stays durably marked for retirement, and the periodic recovery pass retries it with its own `stop_grace` once the new container is verified, even after later deploys. | |
| 165 | + | For a stateless service the old container serves until the new one is ready and routed, then receives SIGTERM and is killed after its `stop_grace` (default 30s). During the overlap both containers share the service's network alias, so other services of the app may reach either one. Database migrations must stay backward compatible with the previous version of the service. A new container that fails readiness is removed and the old one is not touched. An old container that cannot be removed is reported as a cleanup warning without failing the deploy; it stays durably marked for retirement, and the periodic recovery pass retries it with its own `stop_grace` once that pass has verified the new container, even after later deploys. A retirement is only complete once the old container's loopback port claims are released; until then it is retried. `gordon apps stop` and `gordon apps remove` also stop such an old container, and fail, keeping the mark, if they cannot. | |
| 166 | 166 | ||
| 167 | 167 | For a sequential replacement, a failure after the old container was removed is explicit: Gordon does not recreate the old container during that operation and does not promise automatic data rollback. A stateful service's recovery inhibition stays in place until its never-published candidate is confirmed removed; a later boot/start/restart then clears it and rebuilds the generation `ACTIVE` records from its pinned digest, while a candidate that cannot be removed fails recovery closed. Deployment stops at the first service failure: services already deployed in that run are kept and are not rolled back. | |
| 168 | 168 | ||
| @@ -162,7 +162,7 @@ Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─ | |||||
| 162 | 162 | ||||
| 163 | Preflight (image resolution and pull, secrets, volumes, networks, bind policy, reservations) completes before anything is disrupted, so a preflight failure leaves the running service untouched. | 163 | Preflight (image resolution and pull, secrets, volumes, networks, bind policy, reservations) completes before anything is disrupted, so a preflight failure leaves the running service untouched. | ||
| 164 | 164 | ||||
| 165 | - | For a stateless service the old container serves until the new one is ready and routed, then receives SIGTERM and is killed after its `stop_grace` (default 30s). During the overlap both containers share the service's network alias, so other services of the app may reach either one. Database migrations must stay backward compatible with the previous version of the service. A new container that fails readiness is removed and the old one is not touched. An old container that cannot be removed is reported as a cleanup warning without failing the deploy; it stays durably marked for retirement, and the periodic recovery pass retries it with its own `stop_grace` once the new container is verified, even after later deploys. | 165 | + | For a stateless service the old container serves until the new one is ready and routed, then receives SIGTERM and is killed after its `stop_grace` (default 30s). During the overlap both containers share the service's network alias, so other services of the app may reach either one. Database migrations must stay backward compatible with the previous version of the service. A new container that fails readiness is removed and the old one is not touched. An old container that cannot be removed is reported as a cleanup warning without failing the deploy; it stays durably marked for retirement, and the periodic recovery pass retries it with its own `stop_grace` once that pass has verified the new container, even after later deploys. A retirement is only complete once the old container's loopback port claims are released; until then it is retried. `gordon apps stop` and `gordon apps remove` also stop such an old container, and fail, keeping the mark, if they cannot. |
| 166 | 166 | ||||
| 167 | For a sequential replacement, a failure after the old container was removed is explicit: Gordon does not recreate the old container during that operation and does not promise automatic data rollback. A stateful service's recovery inhibition stays in place until its never-published candidate is confirmed removed; a later boot/start/restart then clears it and rebuilds the generation `ACTIVE` records from its pinned digest, while a candidate that cannot be removed fails recovery closed. Deployment stops at the first service failure: services already deployed in that run are kept and are not rolled back. | 167 | For a sequential replacement, a failure after the old container was removed is explicit: Gordon does not recreate the old container during that operation and does not promise automatic data rollback. A stateful service's recovery inhibition stays in place until its never-published candidate is confirmed removed; a later boot/start/restart then clears it and rebuilds the generation `ACTIVE` records from its pinned digest, while a candidate that cannot be removed fails recovery closed. Deployment stops at the first service failure: services already deployed in that run are kept and are not rolled back. | ||
| 168 | 168 | ||||