docs: rewrite Rolling deployments doc from Deployment strategies

This commit is contained in:
Pasha Sviderski
2026-03-01 19:51:47 +10:00
parent 44013da332
commit aef4b2213f
4 changed files with 236 additions and 156 deletions
@@ -78,7 +78,7 @@ See [Push local images to cluster machines](1-deploy-app.md#push-local-images-to
## See also
- [Deploy an app](1-deploy-app.md): Deploy from source code or prebuilt images
- [Deploy an app](1-deploy-app.md): Build and deploy from source code or prebuilt images
- [Deploy a global service](3-deploy-global-services.md): Deploy one service replica on each cluster machine
- [Compose support matrix](../../8-compose-file-reference/1-support-matrix.md): Supported Compose features and Uncloud
extensions
@@ -1,93 +0,0 @@
# Deployment strategies
When you run `uc deploy`, Uncloud updates your services without taking them offline. This page explains how deployments
work and how to configure them for different types of services.
## Rolling deployments
Uncloud uses rolling deployments: it replaces containers one at a time, waiting for each new container to start before
removing the old one. This keeps your service available throughout the update.
For a service with three replicas, the deployment looks like this:
1. Start new container #1
2. Remove old container #1
3. Start new container #2
4. Remove old container #2
5. Start new container #3
6. Remove old container #3
At every step, at least two containers are serving traffic.
:::note
Rolling is currently the only supported deployment strategy.
:::
## Update order
The **update order** controls whether Uncloud starts the new container before or after stopping the old one.
| Order | What happens | Best for |
|-------|--------------|----------|
| `start-first` | Start new container, then stop old | Stateless services (web apps, APIs) |
| `stop-first` | Stop old container, then start new | Stateful services (databases) |
### Default behavior
Uncloud picks the safest default based on your service:
- **Services with host port conflicts** use `stop-first` because ports must be freed first
- **Services with named volumes** (not bind mounts or tmpfs):
- **Single replica** uses `stop-first` to prevent data corruption
- **Multiple replicas** uses `start-first` since concurrent access is already happening
- **All other services** use `start-first` for zero downtime
### Overriding the default
Set `deploy.update_config.order` to override:
```yaml title="compose.yaml"
services:
app:
image: myapp
deploy:
update_config:
order: start-first
volumes:
- app-data:/data
volumes:
app-data:
```
This single-replica service has a volume, so Uncloud would normally use `stop-first`. Setting `order: start-first`
overrides that—useful if your app handles concurrent access safely (like SQLite in WAL mode).
### Choosing the right order
**Use `start-first`** when your service can run multiple instances simultaneously:
- Web applications and API servers
- Background workers processing independent jobs
- Read-heavy services with shared caches
**Use `stop-first`** when your service needs exclusive access:
- Databases (PostgreSQL, MySQL, Redis)
- Services with file locks
- Anything that writes to a volume without coordination
:::warning
Two containers writing to the same volume can corrupt your data. Uncloud defaults to `stop-first` for single-replica
services with volumes, but if you override this or use multiple replicas, make sure your application handles concurrent
access correctly.
:::
## See also
- [Deploy an app](1-deploy-app.md): Build and deploy from source or pre-built images
- [Compose support matrix](../../8-compose-file-reference/1-support-matrix.md): Supported Compose features
@@ -0,0 +1,173 @@
# Rolling deployments
How `uc deploy` updates your services without downtime and automatically rolls back on failure.
Uncloud uses a rolling deployment to update your service by replacing its containers **one at a time**. Before moving on
to the next container, Uncloud waits for the new one to pass [health monitoring](#health-monitoring). If it fails to
become healthy, Uncloud stops the deployment and rolls back that container to the old one. This keeps your service
available throughout the update.
For a service with three replicas and the default `start-first` [update order](#update-order), the deployment looks like
this:
1. Start new container #1, wait until healthy
2. Stop and remove old container #1
3. Start new container #2, wait until healthy
4. Stop and remove old container #2
5. Start new container #3, wait until healthy
6. Stop and remove old container #3
At every step, at least three containers are serving traffic.
## Update order
The **update order** controls whether Uncloud starts the new container before or after stopping the old one.
| Order | What happens | Best for |
|---------------|-----------------------------------------------------------------------------|-------------------------------------|
| `start-first` | Start new container, then stop old<br/>(running containers briefly overlap) | Stateless services (web apps, APIs) |
| `stop-first` | Stop old container, then start new | Stateful services (databases) |
The default is `start-first` so there's **no downtime**. But it automatically switches to `stop-first` in two cases:
- **Host port conflicts**: the old container must free the port before the new one can bind to it.
- **Single-replica service with a volume**: two containers simultaneously writing to the same volume can
**corrupt data**, so Uncloud stops the old container first to prevent this.
`stop-first` can cause a **brief downtime** while the old container stops and the new one starts in these cases. The
deployment plan printed by `uc deploy` indicates which containers will be replaced with `stop-first`.
A multiple-replica service with a volume doesn't automatically switch to `stop-first` as Uncloud assumes that the
concurrent access is desired and safe. Host path and tmpfs mounts don't trigger the switch either.
### Override update order
You can override the update order with `deploy.update_config.order`:
```yaml title="compose.yaml"
services:
app:
image: myapp
volumes:
- data:/data
deploy:
update_config:
order: start-first
volumes:
data:
```
This single-replica service uses a volume, so Uncloud would normally use `stop-first`. Setting `order: start-first`
overrides that.
This is useful if your app handles concurrent access to data safely and you want to avoid downtime. For example, the app
uses an SQLite database in WAL mode on the volume.
## Health monitoring
After starting each new container, Uncloud **monitors** it for failures for **5 seconds** to make sure it keeps running
and not crashing. If it keeps restarting after this period, the deployment fails and Uncloud
[rolls back](#rollback-on-failure) that container to the old one.
This is a safeguard to prevent you from deploying broken code or misconfiguration that would cause downtime. 5 seconds
is typically enough for a process in a container to initialise all its dependencies and start.
You can change the monitoring period for a service with `deploy.update_config.monitor`. For example, increase it if your
app takes longer to start or if you want to give it more time to recover from transient errors on startup.
Set it to `0s` to skip monitoring entirely if you are confident the new containers will start correctly and want to
speed up the deployment.
```yaml title="compose.yaml"
services:
app:
image: myapp
deploy:
update_config:
# Specified as duration: 500ms, 20s, 1m30s, 0s (skip)
monitor: 10s
```
You can also change the default monitoring period (`5s`) for all services globally with an environment variable
`UNCLOUD_HEALTH_MONITOR_PERIOD`:
```shell
export UNCLOUD_HEALTH_MONITOR_PERIOD=10s
# or skip monitoring for all services
export UNCLOUD_HEALTH_MONITOR_PERIOD=0s
```
`deploy.update_config.monitor` overrides the global default for each service.
### Health checks
If your container has a [`healthcheck`](https://github.com/compose-spec/compose-spec/blob/main/spec.md#healthcheck)
configured, Uncloud also checks its health status during and after the monitoring period.
If the container becomes `healthy` before the monitoring period ends, the deployment succeeds early and moves on to the
next container. If the container is `unhealthy` after the monitoring period, Uncloud
[rolls it back](#rollback-on-failure) and fails the deployment. Transient `unhealthy` states during the monitoring
period are tolerated to give the container time to recover from startup issues.
To make deployments **safer** and **faster**, it's recommended to configure a health check that can quickly notify
Uncloud when containers start successfully and become ready to serve traffic. You can configure it with
[`healthcheck`](https://github.com/compose-spec/compose-spec/blob/main/spec.md#healthcheck) in your Compose file or
[`HEALTHCHECK`](https://docs.docker.com/reference/dockerfile#healthcheck) in your image `Dockerfile`:
```yaml title="compose.yaml"
services:
app:
image: myapp
healthcheck:
test: curl -f http://localhost:8000/health
interval: 5s
retries: 3
start_period: 10s
start_interval: 1s
```
:::info important
If a health check fails after the deployment, Uncloud automatically removes the unhealthy container from the
[Caddy](../../3-concepts/1-ingress/1-overview.md) configuration to prevent routing traffic to that container. But it
doesn't automatically restart or roll it back.
Uncloud automatically adds it back to Caddy when it recovers and becomes healthy again. You can inspect the health
status of your containers with [`uc ps`](../../9-cli-reference/uc_ps.md) or
[`uc inspect`](../../9-cli-reference/uc_inspect.md) and check their logs with
[`uc logs`](../../9-cli-reference/uc_logs.md).
:::
### Skip health monitoring
To skip health monitoring for **faster emergency deployments**, use `uc deploy --skip-health`.
:::warning
`--skip-health` won't detect containers that crash on startup or become unhealthy so won't roll them back or stop the
deployment. Use this only for emergency deployments when you are confident the new containers will start correctly.
:::
## Rollback on failure
If a new container fails health monitoring during a deployment, Uncloud stops it but keeps it around so you can inspect
its state and logs. For `stop-first` order, Uncloud also restarts the old container. The deployment then stops and the
remaining containers are left untouched.
For example, if the first container in a rolling update succeeds but the second one fails, the first replacement stays
in place.
## Retry after failure
You can retry the deployment by running `uc deploy` again. Uncloud will skip the successfully deployed containers if the
configuration hasn't changed and only redeploy the remaining ones.
## See also
- [Deploy an app](1-deploy-app.md): Build and deploy from source code or pre-built images
- [Compose support matrix](../../8-compose-file-reference/1-support-matrix.md): Supported Compose features and Uncloud
extensions
@@ -4,7 +4,7 @@ Uncloud supports a subset of the [Compose specification](https://compose-spec.io
The following table shows the support status for main Compose features:
| Feature | Support Status | Notes |
|--------------------|--------------------|---------------------------------------------------------------------------------------------------------------------------------------|
|--------------------|--------------------|----------------------------------------------------------------------------------------------------------------|
| **Services** | | |
| `build` | ✅ Supported | Build context and Dockerfile |
| `cap_add` | ✅ Supported | Additional kernel [capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) |
@@ -48,7 +48,7 @@ The following table shows the support status for main Compose features:
| `resources` | ⚠️ Limited | CPU, memory limits and device reservations |
| `restart_policy` | ❌ Not supported | Defaults to `unless-stopped` |
| `rollback_config` | ❌ Not supported | See [#151](https://github.com/psviderski/uncloud/issues/151) |
| `update_config` | ⚠️ Limited | Only `order` supported (defaults to `start-first`). See [deployment strategies](../4-guides/1-deployments/4-deployment-strategies.md) |
| `update_config` | ⚠️ Limited | `order` and `monitor` supported. See [rolling deployments](../4-guides/1-deployments/4-rolling-deployments.md) |
| **Volumes** | | |
| Named volumes | ✅ Supported | Docker volumes |
| Bind mounts | ✅ Supported | Host path binding |