diff --git a/website/docs/4-guides/1-deploy-app.md b/website/docs/4-guides/1-deploy-app.md
new file mode 100644
index 00000000..42fd124c
--- /dev/null
+++ b/website/docs/4-guides/1-deploy-app.md
@@ -0,0 +1,434 @@
+# Deploy an app
+
+Deploy a containerised application to your Uncloud cluster using either source code or pre-built images.
+
+This guide covers both scenarios:
+
+- **[Deploy from source code](#deploy-from-source-code)**: Build Docker images from your application code and use them
+ to deploy service containers
+- **[Deploy pre-built images](#deploy-pre-built-images)**: Deploy service containers using existing images from a
+ registry or your local machine
+
+Uncloud uses [Compose Specification](https://compose-spec.io/) for defining the deployment configuration of your app's
+services. It implements the most common Compose features with some Uncloud-specific extensions. See
+[Compose support matrix](../8-compose-file-reference/1-support-matrix.md) for details.
+
+## Prerequisites
+
+- `uc` CLI [installed](../2-getting-started/1-install-cli.md) on your local machine
+- An Uncloud cluster with at least one machine (see [Quick start](../2-getting-started/2-deploy-demo-app.md))
+- Basic knowledge of [Compose Specification](https://compose-spec.io/)
+
+## Deploy from source code
+
+Use this scenario when you want to build and deploy your application from
+a [Dockerfile](https://docs.docker.com/reference/dockerfile/) and source code available on your local machine.
+
+### 1. Create a Compose file
+
+Create a `compose.yaml` file in your application directory with a
+[`build`](https://github.com/compose-spec/compose-spec/blob/main/spec.md#build) section for each service you want to
+build. Here is a minimal example of a Compose file that builds and deploys a web app that consists of a single service
+called `web`:
+
+```yaml title="compose.yaml"
+services:
+ web:
+ # Build an image from the Dockerfile in the current directory
+ build: .
+ # Publish the container port 8000 as https://app.example.com
+ x-ports:
+ - app.example.com:8000/https
+```
+
+If you don't have a Dockerfile for building an image from your source code, create one in the same directory.
+
+### 2. Build and deploy your app
+
+To build and deploy services defined in your Compose file, navigate to the directory with `compose.yaml` and run:
+
+```shell
+uc deploy
+```
+
+This command looks for a Compose file in your current working directory and:
+
+1. **Builds images** for services with a `build` section using your local Docker and tags them with the current Git
+ version
+2. **Pushes built images** directly **to cluster machines** using
+ [unregistry](https://github.com/psviderski/unregistry), transferring only the missing layers
+3. **Plans the deployment** and shows you what will change, asking for confirmation
+4. **Creates any missing volumes** on target machines
+5. **Deploys service containers** using the built images and latest configuration changes with zero-downtime rolling
+ updates
+
+See the [uc deploy](../9-cli-reference/uc_deploy.md) reference for all available options.
+
+Watch `uc deploy` building and deploying a demo app (18 seconds):
+
+
+
+### Customise the build configuration
+
+If you need more control over the build configuration, you can specify additional attributes in the `build` section of
+your Compose file. For example, to specify a custom Dockerfile location, set build-time arguments, or build
+multi-platform images.
+
+```yaml
+services:
+ web:
+ build:
+ # Relative path to the directory with your Dockerfile
+ context: ./backend
+ # Set build-time variables defined as ARG in your Dockerfile
+ args:
+ ALPINE_VERSION: 3.22
+ BUILD_ENV: prod
+ # Build a multi-platform image
+ platforms:
+ - linux/amd64
+ - linux/arm64
+```
+
+You can pass additional build arguments or override existing ones using the `--build-arg` flag with `uc deploy`:
+
+```shell
+uc deploy --build-arg BUILD_ENV=dev
+```
+
+You can also use advanced features like build caches, secrets, and SSH access.
+See [Compose Build Specification](https://github.com/compose-spec/compose-spec/blob/main/build.md) for all supported
+attributes.
+
+:::info note
+
+To build multi-platform images, you need to configure your local Docker to use the
+[containerd image store](https://docs.docker.com/desktop/features/containerd/).
+
+:::
+
+### Customise image tags
+
+If you don't specify the `image` attribute, `uc deploy` tags built images with a Git-based version like
+
+```yaml
+# /:.
+myapp/web:2025-10-30-223604.84d33bb
+```
+
+It uses your local date/time if the working directory is not a Git repository.
+
+You can customise the image name and tag format using the `image` attribute. It can be a static name or a dynamic
+template using [environment variables](https://github.com/compose-spec/compose-spec/blob/main/spec.md#interpolation) and
+the [Go template](https://pkg.go.dev/text/template) syntax.
+
+```yaml
+services:
+ web:
+ build: .
+ # Custom image name and tag format
+ image: myapp:{{gitdate "20060102"}}.{{gitsha 7}}.${GITHUB_RUN_ID:-local}{{if .Git.IsDirty}}.dirty{{end}}
+```
+
+This generates tags like:
+
+- `myapp:20251030.84d33bb.local.dirty` when building locally with uncommitted changes
+- `myapp:20251030.84d33bb.1234` when building in CI with `GITHUB_RUN_ID=1234` and a clean repo
+
+`uc deploy` renders the image templates when it loads the Compose file and then uses the resulting names for the build
+and deploy stages.
+
+See the [Image tag format](../8-compose-file-reference/2-image-tag-format.md) reference for all available template
+variables and functions.
+
+### Separate build and deploy steps
+
+You might want to build and deploy services as separate commands. For example, to run them as separate steps in your
+CI/CD pipeline or have more control over the build and deploy process. Here are the commands that are equivalent to
+`uc deploy`:
+
+```shell
+# Build images and push to cluster machines
+uc build --push
+
+# Deploy services using the built images
+uc deploy --no-build
+```
+
+### Deploy configuration changes only
+
+You can use the `--no-build` flag with `uc deploy` to deploy only the configuration changes in your Compose file if your
+source code and images haven't changed. However, the image tag may still change if you're using
+[dynamic tags](#customise-image-tags) based on the Git state (default). In that case, the deploy will likely fail
+because the new tag won't be found on cluster machines.
+
+Use less sensitive dynamic tags or specify the built and pushed images you want to deploy explicitly to avoid this
+issue.
+
+The recommended approach though is to commit all your changes, including the configuration ones, to the repo. Then
+rebuild and deploy your services from a clean repo state. This way, the image tags will always reflect the exact source
+code and configuration used for the deployment.
+
+## Deploy pre-built images
+
+Use this scenario when you want to deploy your application using pre-built images from a container registry (for
+example, Docker Hub or GitHub Container Registry) or your local Docker.
+
+### 1. Create a Compose file
+
+Create a `compose.yaml` file that references an image from a registry using the
+[`image`](https://github.com/compose-spec/compose-spec/blob/main/spec.md#image) attribute. It's important that you don't
+include a `build` section for services using pre-built images. Here is a minimal example of a Compose file that deploys
+an app that consists of a single `nginx` service:
+
+```yaml title="compose.yaml"
+services:
+ nginx:
+ # Use the nginx image from Docker Hub
+ image: nginx:latest
+ # Publish the container port 80 as https://nginx.example.com
+ x-ports:
+ - nginx.example.com:80/https
+```
+
+### 2. Deploy your app
+
+To deploy services defined in your Compose file, navigate to the directory with `compose.yaml` and run:
+
+```shell
+uc deploy
+```
+
+This command looks for a Compose file in your current working directory and:
+
+1. **Plans the deployment** and shows you what will change, asking for confirmation
+2. **Creates any missing volumes** on target machines
+3. **Pulls images** from a registry on cluster machines where services are deployed according to the
+ [`pull_policy`](https://github.com/compose-spec/compose-spec/blob/main/spec.md#pull_policy)
+4. **Deploys service containers** using the pulled images and latest configuration changes with zero-downtime rolling
+ updates
+
+### Control image pulling
+
+By default, `uc deploy` pulls an image from a registry only if it's missing on a target machine. You can change this
+behavior using the [`pull_policy`](https://github.com/compose-spec/compose-spec/blob/main/spec.md#pull_policy)
+attribute. For example, to always pull the latest version of an image before deploying.
+
+```yaml
+services:
+ nginx:
+ image: nginx:alpine
+ # Always pull the latest :alpine tag before deploying
+ pull_policy: always
+```
+
+Available `pull_policy` values:
+
+- `always`: Always pull the image from the registry before deploying
+- `missing` (default): Pull only if the image isn't available on the target machine
+- `never`: Never pull, the image must be present on the target machine or the deploy will fail
+
+### Pull from a private registry
+
+If your images are in a private registry, `uc deploy` needs an authentication token to pull them. You can provide it by
+either:
+
+- Logging in to the registry using your local Docker (recommended)
+- Logging in to the registry using Docker on each cluster machine you want to deploy to
+
+When you're logged in using your local Docker, `uc deploy` automatically passes your local Docker credentials for the
+private registry to cluster machines when pulling images. This way, you don't need to log in on each machine manually.
+
+See [`docker login`](https://docs.docker.com/reference/cli/docker/login/) for instructions on how to log in to a private
+registry.
+
+### Push local images to cluster machines
+
+You can also deploy pre-built images that exist only in your local Docker and are not available for pulling on cluster
+machines. For example, images you built locally outside of the Compose workflow or pulled from a private registry that
+is unreachable from your cluster machines. This is useful for deploying to air-gapped or restricted environments.
+
+You can push these local images directly to your cluster machines using the
+[`uc image push`](../9-cli-reference/uc_image_push.md) command:
+
+```shell
+# Push to *all* cluster machines
+uc image push myapp:latest
+
+# Push to specific machines only
+uc image push myapp:latest -m machine1,machine2
+```
+
+This command uploads the image from your local Docker to the cluster machines using
+[unregistry](https://github.com/psviderski/unregistry) running as part of the Uncloud daemon on each machine. It
+efficiently transfers only the image layers that don't already exist on the target machines.
+
+After pushing the image, update your Compose file to reference it:
+
+```yaml
+services:
+ web:
+ # Reference the image you just pushed
+ image: myapp:latest
+ # Never try to pull from a registry since the image is only available locally
+ pull_policy: never
+```
+
+Run [`uc images`](../9-cli-reference/uc_images.md) to verify that the image is available on the target machines.
+
+Then deploy as usual:
+
+```shell
+uc deploy
+```
+
+:::tip
+
+Set `pull_policy: never` when using local images to prevent `uc deploy` from trying to pull them from a registry.
+
+:::
+
+## Mix source builds and pre-built images
+
+Define multiple services in your Compose file, mixing source builds and pre-built images:
+
+```yaml
+services:
+ web:
+ # Build web service from source
+ build: .
+ x-ports:
+ - app.example.com:8000/https
+
+ db:
+ # Pull a pre-built PostgreSQL image from Docker Hub
+ image: postgres:18
+ environment:
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+```
+
+`uc deploy` builds and pushes images for services with a `build` section and pulls images from a registry for services
+with only an `image` attribute.
+
+:::warning
+
+**Service names must be globally unique** across all Compose files deployed to the same cluster.
+
+Unlike Docker Compose or Docker Swarm, Uncloud doesn't automatically prefix service names with project or stack names.
+Choose unique names to avoid conflicts with services deployed from other Compose files.
+
+:::
+
+## Deploy to specific machines
+
+By default, Uncloud randomly chooses available machines to run your services on, spreading multiple replicas of a
+service across all machines for high availability. You can restrict which machines can run your service using the
+[`x-machines`](../8-compose-file-reference/1-support-matrix.md#x-machines) extension in your Compose file.
+
+This is useful when you want to:
+
+- Deploy services to machines in a specific region or data center
+- Run services only on machines with specific hardware (for example, GPUs or ARM processors)
+- Keep certain services isolated to dedicated machines
+- Deploy to a subset of machines for testing before rolling out cluster-wide
+
+Here's a basic example:
+
+```yaml
+services:
+ web:
+ build: .
+ x-ports:
+ - app.example.com:8000/https
+ # Spread 3 replicas across machine-1 and machine-2 only
+ scale: 3
+ x-machines:
+ - machine-1
+ - machine-2
+
+ db:
+ image: postgres:18
+ environment:
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ volumes:
+ - db-data:/var/lib/postgresql
+ # Create the db-data volume and run the DB container on machine-db
+ x-machines: machine-db
+
+volumes:
+ db-data:
+```
+
+When you deploy this Compose file with `uc deploy`:
+
+- The `web` service will create and spread its 3 replicas only across `machine-1` and `machine-2`
+- The `db` service will create its `db-data` volume and run only on `machine-db`
+
+:::tip
+
+Use [`uc machine ls`](../9-cli-reference/uc_machine_ls.md) to see available machines in your cluster.
+
+:::
+
+### Push images to specific machines only
+
+When building from source, `uc deploy` and `uc build --push` automatically push images to **all** cluster machines by
+default. This ensures images are available wherever services might be deployed.
+
+If you're using `x-machines` to restrict deployments to specific machines, `uc deploy` and `uc build --push` push images
+only to those machines. This saves time and bandwidth by uploading images only where they're needed.
+
+## Run one replica on each machine
+
+To deploy exactly one service replica on each machine in your cluster, set
+[`mode: global`](https://github.com/compose-spec/compose-spec/blob/main/deploy.md#mode) under the `deploy` section. It's
+useful for cluster-wide services like monitoring agents, log collectors, or reverse proxies.
+
+You can also combine `mode: global` with `x-machines` to run one container on each machine in a specific set.
+
+```yaml
+services:
+ monitoring:
+ image: quay.io/prometheus/node-exporter:latest
+ deploy:
+ # Run one container on each machine in the cluster
+ mode: global
+ # Optionally run one container on each of these specific machines only
+ #x-machines:
+ # - machine-1
+ # - machine-2
+```
+
+## Use a different Compose file location
+
+If your Compose file has a different name or location, use the `-f/--file` flag to specify its path:
+
+```shell
+uc deploy -f path/to/your-compose.yaml
+```
+
+You can also specify multiple Compose files to merge configurations:
+
+```shell
+uc deploy -f compose.yaml -f compose.prod.yaml
+```
+
+See [Use multiple Compose files](https://docs.docker.com/compose/how-tos/multiple-compose-files/) for details on how you
+can customise your Compose application for different environments or workflows.
+
+## Verify your deployment
+
+After deploying your app, you can verify that your services are running as expected by listing all deployed services in
+the cluster:
+
+```shell
+uc ls
+```
+
+and inspecting the status of containers for a specific service:
+
+```shell
+uc inspect web
+```
diff --git a/website/docs/8-compose-file-reference/2-image-tag-format.md b/website/docs/8-compose-file-reference/2-image-tag-format.md
new file mode 100644
index 00000000..5fb0bde5
--- /dev/null
+++ b/website/docs/8-compose-file-reference/2-image-tag-format.md
@@ -0,0 +1,3 @@
+# Image tag format
+
+TBD