diff --git a/website/docs/4-guides/1-deployments/2-deploy-specific-machines.md b/website/docs/4-guides/1-deployments/2-deploy-specific-machines.md index 27400a39..a89dd115 100644 --- a/website/docs/4-guides/1-deployments/2-deploy-specific-machines.md +++ b/website/docs/4-guides/1-deployments/2-deploy-specific-machines.md @@ -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 diff --git a/website/docs/4-guides/1-deployments/4-deployment-strategies.md b/website/docs/4-guides/1-deployments/4-deployment-strategies.md deleted file mode 100644 index d41e5d2e..00000000 --- a/website/docs/4-guides/1-deployments/4-deployment-strategies.md +++ /dev/null @@ -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 diff --git a/website/docs/4-guides/1-deployments/4-rolling-deployments.md b/website/docs/4-guides/1-deployments/4-rolling-deployments.md new file mode 100644 index 00000000..1df274ae --- /dev/null +++ b/website/docs/4-guides/1-deployments/4-rolling-deployments.md @@ -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
(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 diff --git a/website/docs/8-compose-file-reference/1-support-matrix.md b/website/docs/8-compose-file-reference/1-support-matrix.md index 7cf57924..c1127dd4 100644 --- a/website/docs/8-compose-file-reference/1-support-matrix.md +++ b/website/docs/8-compose-file-reference/1-support-matrix.md @@ -3,68 +3,68 @@ Uncloud supports a subset of the [Compose specification](https://compose-spec.io/) with some extensions and limitations. 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) | -| `cap_drop` | ✅ Supported | Which kernel [capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) to drop | -| `command` | ✅ Supported | Override container command | -| `configs` | ✅ Supported | File-based and inline configs | -| `cpus` | ✅ Supported | CPU limit | -| `depends_on` | ⚠️ Limited | Services deployed in order but conditions not checked | -| `devices` | ✅ Supported | Device mappings | -| `dns` | ❌ Not supported | Built-in service discovery | -| `dns_search` | ❌ Not supported | Built-in service discovery | -| `entrypoint` | ✅ Supported | Override container entrypoint | -| `env_file` | ✅ Supported | Environment file | -| `environment` | ✅ Supported | Environment variables | -| `gpus` | ✅ Supported | GPU device access | -| `healthcheck` | ✅ Supported | Health check configuration | -| `image` | ✅ Supported | Container image specification | -| `init` | ✅ Supported | Run init process in container | -| `labels` | ❌ Not supported | | -| `links` | ❌ Not supported | Use service names for communication | -| `logging` | ✅ Supported | Defaults to [local](https://docs.docker.com/engine/logging/drivers/local/) log driver | -| `mem_limit` | ✅ Supported | Memory limit | -| `mem_reservation` | ✅ Supported | Memory reservation | -| `mem_swappiness` | ❌ Not supported | | -| `memswap_limit` | ❌ Not supported | | -| `networks` | ❌ Not supported | All containers share cluster network | -| `ports` | ⚠️ Limited | `mode: host` only, use [`x-ports`](#x-ports) for HTTP/HTTPS | -| `privileged` | ✅ Supported | Run containers in privileged mode | -| `pull_policy` | ✅ Supported | `always`, `missing`, `never` | -| `secrets` | ❌ Not supported | Use configs or environment variables | -| `security_opt` | ❌ Not supported | | -| `storage_opt` | ❌ Not supported | | -| `sysctls` | ✅ Supported | Namespaced kernel parameters | -| `user` | ✅ Supported | Set container user | -| `volumes` | ✅ Supported | Named volumes, bind mounts, tmpfs | -| **Deploy** | | | -| `labels` | ❌ Not supported | | -| `mode` | ✅ Supported | Either `global` or `replicated` | -| `placement` | ❌ Not supported | Use [`x-machines`](#x-machines) extension | -| `replicas` | ✅ Supported | Number of container replicas | -| `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) | -| **Volumes** | | | -| Named volumes | ✅ Supported | Docker volumes | -| Bind mounts | ✅ Supported | Host path binding | -| Tmpfs mounts | ✅ Supported | In-memory filesystems | -| Volume labels | ✅ Supported | Custom labels | -| External volumes | ✅ Supported | Must exist before deployment | -| Volume drivers | ⚠️ Limited | Local driver only | -| **Configs** | | | -| File-based configs | ✅ Supported | Read from file | -| Inline configs | ✅ Supported | Defined in compose file | -| External configs | ❌ Not supported | Not supported | -| Short syntax | ❌ Not supported | Use long syntax only | -| **Extensions** | | | -| `x-caddy` | ✅ Uncloud-specific | Custom Caddy configuration | -| `x-machines` | ✅ Uncloud-specific | Machine placement constraints | -| `x-ports` | ✅ Uncloud-specific | Service port publishing | +| 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) | +| `cap_drop` | ✅ Supported | Which kernel [capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) to drop | +| `command` | ✅ Supported | Override container command | +| `configs` | ✅ Supported | File-based and inline configs | +| `cpus` | ✅ Supported | CPU limit | +| `depends_on` | ⚠️ Limited | Services deployed in order but conditions not checked | +| `devices` | ✅ Supported | Device mappings | +| `dns` | ❌ Not supported | Built-in service discovery | +| `dns_search` | ❌ Not supported | Built-in service discovery | +| `entrypoint` | ✅ Supported | Override container entrypoint | +| `env_file` | ✅ Supported | Environment file | +| `environment` | ✅ Supported | Environment variables | +| `gpus` | ✅ Supported | GPU device access | +| `healthcheck` | ✅ Supported | Health check configuration | +| `image` | ✅ Supported | Container image specification | +| `init` | ✅ Supported | Run init process in container | +| `labels` | ❌ Not supported | | +| `links` | ❌ Not supported | Use service names for communication | +| `logging` | ✅ Supported | Defaults to [local](https://docs.docker.com/engine/logging/drivers/local/) log driver | +| `mem_limit` | ✅ Supported | Memory limit | +| `mem_reservation` | ✅ Supported | Memory reservation | +| `mem_swappiness` | ❌ Not supported | | +| `memswap_limit` | ❌ Not supported | | +| `networks` | ❌ Not supported | All containers share cluster network | +| `ports` | ⚠️ Limited | `mode: host` only, use [`x-ports`](#x-ports) for HTTP/HTTPS | +| `privileged` | ✅ Supported | Run containers in privileged mode | +| `pull_policy` | ✅ Supported | `always`, `missing`, `never` | +| `secrets` | ❌ Not supported | Use configs or environment variables | +| `security_opt` | ❌ Not supported | | +| `storage_opt` | ❌ Not supported | | +| `sysctls` | ✅ Supported | Namespaced kernel parameters | +| `user` | ✅ Supported | Set container user | +| `volumes` | ✅ Supported | Named volumes, bind mounts, tmpfs | +| **Deploy** | | | +| `labels` | ❌ Not supported | | +| `mode` | ✅ Supported | Either `global` or `replicated` | +| `placement` | ❌ Not supported | Use [`x-machines`](#x-machines) extension | +| `replicas` | ✅ Supported | Number of container replicas | +| `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 | `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 | +| Tmpfs mounts | ✅ Supported | In-memory filesystems | +| Volume labels | ✅ Supported | Custom labels | +| External volumes | ✅ Supported | Must exist before deployment | +| Volume drivers | ⚠️ Limited | Local driver only | +| **Configs** | | | +| File-based configs | ✅ Supported | Read from file | +| Inline configs | ✅ Supported | Defined in compose file | +| External configs | ❌ Not supported | Not supported | +| Short syntax | ❌ Not supported | Use long syntax only | +| **Extensions** | | | +| `x-caddy` | ✅ Uncloud-specific | Custom Caddy configuration | +| `x-machines` | ✅ Uncloud-specific | Machine placement constraints | +| `x-ports` | ✅ Uncloud-specific | Service port publishing | ### Legend