detached 7d22851

docs(deployment): exclude UDP from the overlap and describe marker-based retirement

commit
7d228517dc9ce791ae27ee74fdb1163786a69fa7
author
brice · · 2026-10-16 19:47 UTC
parent
733740b
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
7 files · +16 -16
M README.md +1 -1
@@ -154,7 +154,7 @@ gordon apps deploy blog --remote prod
154154 - Private Docker/Podman registry on your VPS
155155 - Declarative apps: one TOML file per app, explicit apply then deploy
156156 - Domain-to-container routing through a smart TCP edge reverse proxy
157-- Zero-downtime replacement of stateless services (start, verify readiness, switch traffic, stop the old one); services with volumes, binds, or devices are replaced sequentially
157+- Zero-downtime replacement of stateless TCP/HTTP services (start, verify readiness, switch traffic, stop the old one); services with volumes, binds, devices, or UDP interfaces are replaced sequentially
158158 - Remote CLI management (daemon is the sole writer)
159159 - Declarative per-service volumes, retained across lifecycle operations
160160 - Per-service secrets in pass, app-wide public env in the manifest
@@ -154,7 +154,7 @@ gordon apps deploy blog --remote prod
154 - Private Docker/Podman registry on your VPS154 - Private Docker/Podman registry on your VPS
155 - Declarative apps: one TOML file per app, explicit apply then deploy155 - Declarative apps: one TOML file per app, explicit apply then deploy
156 - Domain-to-container routing through a smart TCP edge reverse proxy156 - Domain-to-container routing through a smart TCP edge reverse proxy
157-- Zero-downtime replacement of stateless services (start, verify readiness, switch traffic, stop the old one); services with volumes, binds, or devices are replaced sequentially157+- Zero-downtime replacement of stateless TCP/HTTP services (start, verify readiness, switch traffic, stop the old one); services with volumes, binds, devices, or UDP interfaces are replaced sequentially
158 - Remote CLI management (daemon is the sole writer)158 - Remote CLI management (daemon is the sole writer)
159 - Declarative per-service volumes, retained across lifecycle operations159 - Declarative per-service volumes, retained across lifecycle operations
160 - Per-service secrets in pass, app-wide public env in the manifest160 - Per-service secrets in pass, app-wide public env in the manifest
M docs/concepts.md +5 -5
@@ -71,19 +71,19 @@ DATABASE_URL = "database-url"
7171
7272 ## Updates
7373
74-Gordon deploys an app's services one at a time in sorted order. A service with no volume, bind, or device is replaced without downtime; every other service is replaced sequentially and may be briefly interrupted, because Gordon never runs two Gordon-managed generations of a service that owns a volume, a bind, or a device.
74+Gordon deploys an app's services one at a time in sorted order. A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime; every other service is replaced sequentially and may be briefly interrupted. Gordon never runs two Gordon-managed generations of a service that owns a volume, a bind, or a device, and a UDP service is excluded because UDP sessions stay pinned to the backend they started on.
7575
7676 Preflight completes before anything is disrupted (image resolution and pull, secrets, volumes, networks, bind policy, reservations). If preflight fails, the running service is left untouched.
7777
78-A stateless service (no volume, bind, or device) is replaced with a short overlap:
78+A stateless service (no volume, bind, device, or UDP interface) is replaced with a short overlap:
7979
8080 ```
8181 Preflight ─► start new ─► readiness ─► publish ACTIVE ─► route traffic ─► SIGTERM old ─► stop_grace ─► kill + remove old
8282 ```
8383
84-The old container keeps serving and stays routed until the new one is ready and traffic points at it. Only then does the old container receive SIGTERM, with its own `stop_grace` (default 30s) before it is killed. While both run, they share the service's network alias, so a request from another service of the app may reach either generation. A database migration must therefore stay backward compatible with the previous version of the service. If the new container never becomes ready, it is removed and the old one is not touched. If the old container cannot be removed after the switch, the deploy still succeeds and reports a cleanup warning, and the next reconciliation retries it.
84+The old container keeps serving and stays routed until the new one is ready and traffic points at it. Only then does the old container receive SIGTERM, with its own `stop_grace` (default 30s) before it is killed. While both run, they share the service's network alias, so a request from another service of the app may reach either generation. A database migration must therefore stay backward compatible with the previous version of the service. If the new container never becomes ready, it is removed and the old one is not touched. If the old container cannot be removed after the switch, the deploy still succeeds and reports a cleanup warning. Before the switch Gordon durably marks the old container for retirement, so the periodic recovery pass keeps retrying it, with the old container's `stop_grace`, once the new container is verified; later deploys do not lose the cleanup.
8585
86-A service with a volume, a bind, or a device is replaced sequentially:
86+A service with a volume, a bind, a device, or a UDP interface is replaced sequentially:
8787
8888 ```
8989 Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─► readiness ─► publish ACTIVE ─► route traffic
@@ -93,7 +93,7 @@ If replacement fails after the old container was removed, the failure is explici
9393
9494 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.
9595
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; Gordon stops the old one at the next reconciliation, after re-applying routing to the published container. 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 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.
9797
9898 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`.
9999
@@ -71,19 +71,19 @@ DATABASE_URL = "database-url"
71 71
72 ## Updates72 ## Updates
73 73
74-Gordon deploys an app's services one at a time in sorted order. A service with no volume, bind, or device is replaced without downtime; every other service is replaced sequentially and may be briefly interrupted, because Gordon never runs two Gordon-managed generations of a service that owns a volume, a bind, or a device.74+Gordon deploys an app's services one at a time in sorted order. A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime; every other service is replaced sequentially and may be briefly interrupted. Gordon never runs two Gordon-managed generations of a service that owns a volume, a bind, or a device, and a UDP service is excluded because UDP sessions stay pinned to the backend they started on.
75 75
76 Preflight completes before anything is disrupted (image resolution and pull, secrets, volumes, networks, bind policy, reservations). If preflight fails, the running service is left untouched.76 Preflight completes before anything is disrupted (image resolution and pull, secrets, volumes, networks, bind policy, reservations). If preflight fails, the running service is left untouched.
77 77
78-A stateless service (no volume, bind, or device) is replaced with a short overlap:78+A stateless service (no volume, bind, device, or UDP interface) is replaced with a short overlap:
79 79
80 ```80 ```
81 Preflight ─► start new ─► readiness ─► publish ACTIVE ─► route traffic ─► SIGTERM old ─► stop_grace ─► kill + remove old81 Preflight ─► start new ─► readiness ─► publish ACTIVE ─► route traffic ─► SIGTERM old ─► stop_grace ─► kill + remove old
82 ```82 ```
83 83
84-The old container keeps serving and stays routed until the new one is ready and traffic points at it. Only then does the old container receive SIGTERM, with its own `stop_grace` (default 30s) before it is killed. While both run, they share the service's network alias, so a request from another service of the app may reach either generation. A database migration must therefore stay backward compatible with the previous version of the service. If the new container never becomes ready, it is removed and the old one is not touched. If the old container cannot be removed after the switch, the deploy still succeeds and reports a cleanup warning, and the next reconciliation retries it.84+The old container keeps serving and stays routed until the new one is ready and traffic points at it. Only then does the old container receive SIGTERM, with its own `stop_grace` (default 30s) before it is killed. While both run, they share the service's network alias, so a request from another service of the app may reach either generation. A database migration must therefore stay backward compatible with the previous version of the service. If the new container never becomes ready, it is removed and the old one is not touched. If the old container cannot be removed after the switch, the deploy still succeeds and reports a cleanup warning. Before the switch Gordon durably marks the old container for retirement, so the periodic recovery pass keeps retrying it, with the old container's `stop_grace`, once the new container is verified; later deploys do not lose the cleanup.
85 85
86-A service with a volume, a bind, or a device is replaced sequentially:86+A service with a volume, a bind, a device, or a UDP interface is replaced sequentially:
87 87
88 ```88 ```
89 Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─► readiness ─► publish ACTIVE ─► route traffic89 Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─► readiness ─► publish ACTIVE ─► route traffic
@@ -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; Gordon stops the old one at the next reconciliation, after re-applying routing to the published container. 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 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.
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 +2 -2
@@ -114,9 +114,9 @@ Image registry names and digest syntax are validated during manifest apply, reso
114114
115115 A deploy recreates a service whose image digest, spec, environment, or secret values changed and keeps an unchanged one.
116116
117-- A service with no volume, bind, or device (stateless) 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.
118118 - 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, or device 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.
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.
120120
121121 ## Environment and Secrets
122122
@@ -114,9 +114,9 @@ Image registry names and digest syntax are validated during manifest apply, reso
114 114
115 A deploy recreates a service whose image digest, spec, environment, or secret values changed and keeps an unchanged one.115 A deploy recreates a service whose image digest, spec, environment, or secret values changed and keeps an unchanged one.
116 116
117-- A service with no volume, bind, or device (stateless) 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, or device 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.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.
120 120
121 ## Environment and Secrets121 ## Environment and Secrets
122 122
M docs/deployment/index.md +5 -5
@@ -146,15 +146,15 @@ docker push gordon.example.com/myapp:$VERSION
146146
147147 ## Updates
148148
149-Gordon deploys an app's services one at a time in sorted order. A service with no volume, bind, or device is replaced without downtime; a service that owns one of them is replaced sequentially and may be briefly interrupted, because Gordon never runs two Gordon-managed generations of such a service at the same time.
149+Gordon deploys an app's services one at a time in sorted order. A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime; a service that owns a volume, bind, or device is replaced sequentially and may be briefly interrupted, because Gordon never runs two Gordon-managed generations of such a service at the same time. A UDP service is also replaced sequentially: UDP sessions stay pinned to the backend they started on, so they could not follow the switch.
150150
151-Stateless service (no volume, bind, or device):
151+Stateless service (no volume, bind, device, or UDP interface):
152152
153153 ```
154154 Preflight ─► start new ─► readiness ─► publish ACTIVE ─► route traffic ─► SIGTERM old ─► stop_grace ─► kill + remove old
155155 ```
156156
157-Service with a volume, bind, or device:
157+Service with a volume, bind, device, or UDP interface:
158158
159159 ```
160160 Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─► readiness ─► publish ACTIVE ─► route traffic
@@ -162,11 +162,11 @@ Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─
162162
163163 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.
164164
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, and reconciliation retries it.
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.
166166
167167 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.
168168
169-If a deploy is interrupted between creating the replacement container and publishing it, that candidate is recorded durably and Gordon removes it before creating or rebuilding any generation of that app — at boot and before every mutation; a stateless service's old container keeps serving. If it is interrupted after the switch but before the old container was stopped, the next reconciliation re-applies routing and stops the old container. 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. A failed final traffic publication is republished by the next recovery pass, within 15 seconds by default.
169+If a deploy is interrupted between creating the replacement container and publishing it, that candidate is recorded durably and Gordon removes it before creating or rebuilding any generation of that app — at boot and before every mutation; a stateless service's old container keeps serving. If it is interrupted after the switch but before the old container was stopped, the periodic recovery pass verifies and routes the published container and then stops the old one. 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. A failed final traffic publication is republished by the next recovery pass, within 15 seconds by default.
170170
171171 ## Related
172172
@@ -146,15 +146,15 @@ docker push gordon.example.com/myapp:$VERSION
146 146
147 ## Updates147 ## Updates
148 148
149-Gordon deploys an app's services one at a time in sorted order. A service with no volume, bind, or device is replaced without downtime; a service that owns one of them is replaced sequentially and may be briefly interrupted, because Gordon never runs two Gordon-managed generations of such a service at the same time.149+Gordon deploys an app's services one at a time in sorted order. A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime; a service that owns a volume, bind, or device is replaced sequentially and may be briefly interrupted, because Gordon never runs two Gordon-managed generations of such a service at the same time. A UDP service is also replaced sequentially: UDP sessions stay pinned to the backend they started on, so they could not follow the switch.
150 150
151-Stateless service (no volume, bind, or device):151+Stateless service (no volume, bind, device, or UDP interface):
152 152
153 ```153 ```
154 Preflight ─► start new ─► readiness ─► publish ACTIVE ─► route traffic ─► SIGTERM old ─► stop_grace ─► kill + remove old154 Preflight ─► start new ─► readiness ─► publish ACTIVE ─► route traffic ─► SIGTERM old ─► stop_grace ─► kill + remove old
155 ```155 ```
156 156
157-Service with a volume, bind, or device:157+Service with a volume, bind, device, or UDP interface:
158 158
159 ```159 ```
160 Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─► readiness ─► publish ACTIVE ─► route traffic160 Preflight ─► withdraw traffic ─► stop + remove old ─► start new ─► readiness ─► publish ACTIVE ─► route traffic
@@ -162,11 +162,11 @@ 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, and reconciliation retries it.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.
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
169-If a deploy is interrupted between creating the replacement container and publishing it, that candidate is recorded durably and Gordon removes it before creating or rebuilding any generation of that app — at boot and before every mutation; a stateless service's old container keeps serving. If it is interrupted after the switch but before the old container was stopped, the next reconciliation re-applies routing and stops the old container. 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. A failed final traffic publication is republished by the next recovery pass, within 15 seconds by default.169+If a deploy is interrupted between creating the replacement container and publishing it, that candidate is recorded durably and Gordon removes it before creating or rebuilding any generation of that app — at boot and before every mutation; a stateless service's old container keeps serving. If it is interrupted after the switch but before the old container was stopped, the periodic recovery pass verifies and routes the published container and then stops the old one. 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. A failed final traffic publication is republished by the next recovery pass, within 15 seconds by default.
170 170
171 ## Related171 ## Related
172 172
M docs/deployment/rollback.md +1 -1
@@ -12,7 +12,7 @@ gordon apps apply --file blog.toml --remote prod
1212 gordon apps deploy blog --remote prod
1313 ```
1414
15-A rollback deploy replaces services one at a time like any other deploy: a stateless service is replaced without downtime, while a service with a volume, bind, or device is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If a sequential replacement fails after the old container was removed, Gordon does not recreate it and does not promise automatic data rollback.
15+A rollback deploy replaces services one at a time like any other deploy: a stateless TCP or HTTP service is replaced without downtime, while a service with a volume, bind, device, or UDP interface is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If a sequential replacement fails after the old container was removed, Gordon does not recreate it and does not promise automatic data rollback.
1616
1717 ## Re-push the Previous Image as latest
1818
@@ -12,7 +12,7 @@ gordon apps apply --file blog.toml --remote prod
12 gordon apps deploy blog --remote prod12 gordon apps deploy blog --remote prod
13 ```13 ```
14 14
15-A rollback deploy replaces services one at a time like any other deploy: a stateless service is replaced without downtime, while a service with a volume, bind, or device is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If a sequential replacement fails after the old container was removed, Gordon does not recreate it and does not promise automatic data rollback.15+A rollback deploy replaces services one at a time like any other deploy: a stateless TCP or HTTP service is replaced without downtime, while a service with a volume, bind, device, or UDP interface is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If a sequential replacement fails after the old container was removed, Gordon does not recreate it and does not promise automatic data rollback.
16 16
17 ## Re-push the Previous Image as latest17 ## Re-push the Previous Image as latest
18 18
M docs/getting-started.md +1 -1
@@ -197,7 +197,7 @@ gordon images push myapp --build --remote prod
197197 gordon apps deploy blog --remote prod
198198 ```
199199
200-Gordon deploys services one at a time in sorted order. A service with no volume, bind, or device is replaced without downtime: the new container starts and becomes ready while the old one keeps serving, then traffic switches and the old one is stopped. A service that owns a volume, bind, or device is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If preflight fails, the running service is left untouched.
200+Gordon deploys services one at a time in sorted order. A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime: the new container starts and becomes ready while the old one keeps serving, then traffic switches and the old one is stopped. A service that owns a volume, bind, device, or UDP interface is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If preflight fails, the running service is left untouched.
201201
202202 ## Next Steps
203203
@@ -197,7 +197,7 @@ gordon images push myapp --build --remote prod
197 gordon apps deploy blog --remote prod197 gordon apps deploy blog --remote prod
198 ```198 ```
199 199
200-Gordon deploys services one at a time in sorted order. A service with no volume, bind, or device is replaced without downtime: the new container starts and becomes ready while the old one keeps serving, then traffic switches and the old one is stopped. A service that owns a volume, bind, or device is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If preflight fails, the running service is left untouched.200+Gordon deploys services one at a time in sorted order. A stateless TCP or HTTP service (no volume, bind, device, or UDP interface) is replaced without downtime: the new container starts and becomes ready while the old one keeps serving, then traffic switches and the old one is stopped. A service that owns a volume, bind, device, or UDP interface is replaced sequentially, may be briefly interrupted, and never has two generations running at once. If preflight fails, the running service is left untouched.
201 201
202 ## Next Steps202 ## Next Steps
203 203
M docs/index.md +1 -1
@@ -10,7 +10,7 @@ Gordon runs on your VPS and provides:
1010 - **Declarative Apps** - One TOML file per app, explicit apply then deploy
1111 - **HTTP Reverse Proxy** - Routes app hosts to containers
1212 - **Push, Apply, Deploy** - Push stores images; deploy is always explicit
13-- **Zero-Downtime Stateless Deploys** - Stateless services start the new generation, verify readiness, switch traffic, then stop the old one; services with volumes, binds, or devices are replaced sequentially
13+- **Zero-Downtime Stateless Deploys** - Stateless services start the new generation, verify readiness, switch traffic, then stop the old one; services with volumes, binds, devices, or UDP interfaces are replaced sequentially
1414 - **Single Binary** - ~15MB RAM footprint
1515
1616 ## How It Works
@@ -10,7 +10,7 @@ Gordon runs on your VPS and provides:
10 - **Declarative Apps** - One TOML file per app, explicit apply then deploy10 - **Declarative Apps** - One TOML file per app, explicit apply then deploy
11 - **HTTP Reverse Proxy** - Routes app hosts to containers11 - **HTTP Reverse Proxy** - Routes app hosts to containers
12 - **Push, Apply, Deploy** - Push stores images; deploy is always explicit12 - **Push, Apply, Deploy** - Push stores images; deploy is always explicit
13-- **Zero-Downtime Stateless Deploys** - Stateless services start the new generation, verify readiness, switch traffic, then stop the old one; services with volumes, binds, or devices are replaced sequentially13+- **Zero-Downtime Stateless Deploys** - Stateless services start the new generation, verify readiness, switch traffic, then stop the old one; services with volumes, binds, devices, or UDP interfaces are replaced sequentially
14 - **Single Binary** - ~15MB RAM footprint14 - **Single Binary** - ~15MB RAM footprint
15 15
16 ## How It Works16 ## How It Works

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close