telepresence

GitHub

Local development against a remote Kubernetes or OpenShift cluster

RAW Compose
1. Pull Image (DOCKERHUB) docker pull telepresenceio/telepresence
2. Docker Run (1-Click Launch) docker run -d --name telepresence --restart always telepresenceio/telepresence:latest
3. Podman Run podman run -d --name telepresence --restart always telepresenceio/telepresence:latest
4. Skopeo Inspect (Zero-Pull Manifest) skopeo inspect docker://docker.io/telepresenceio/telepresence:latest
5. ORAS Pull (OCI Artifact) oras pull telepresenceio/telepresence:latest
6. Compose Deployment docker compose up -d
docs/howtos/docker-compose.md
Download .yml
---
title: "Using Telepresence with Docker Compose"
hide_table_of_contents: true
---
# Extend Docker Compose with Telepresence

A Docker Compose file can contain extensions that Docker Compose ignores. The `telepresence compose` command functions similarly to `docker compose`, but will process any `x-tele` extensions present in the Docker Compose file or its overrides before passing the final Compose specification to Docker Compose.

The `x-tele` extensions are particularly useful when you have a set of services defined in a Docker Compose file that mirrors services running in a cluster, and you want your local services to interact with remote services or vice versa. The `x-tele` extensions enable your Compose services to either act as handlers when telepresence attaches to a remote service, or to temporarily act as proxies for remotely running services.

The extensions can be added directly to the `compose.yaml` file, or to a `compose.override.yaml` (merged automatically by Docker Compose).

## The `x-tele` extensions

Telepresence recognizes an `x-tele` extension at the top level of the Compose file — defining
connections to the cluster and volume mount policies — and per-service `x-tele` extensions of
type `connect`, `proxy`, `ingest`, `intercept`, `replace`, or `wiretap`, each mirroring the
telepresence command of the same name. All service types imply a `connect`. The complete field
reference for every extension is in
[Telepresence Docker Compose Extension](../reference/compose.md); this page walks through using
them.

## Walkthrough and Samples
This documentation will give some examples on how to use the `x-tele` extension using the sample Emoji application, originally developed by Buoyant.io, from the https://github.com/telepresenceio/emojivoto repository. This app is easy to deploy locally using `docker compose up` or remotely to a cluster using `kubectl apply --kustomize`.

### Initial Steps

The examples assume that you have [installed](../install/manager.md) the Telepresence Traffic Manager in your cluster.

We start by ensuring that the Emojivoto application can be deployed, both locally using `docker compose up` and remotely in your cluster.

#### 1. Download the Emojivoto App

Clone the emojivoto git repository with the following command:
```console
$ git clone https://github.com/telepresenceio/emojivoto.git
```

#### 2. Use the app locally

```console
$ cd emojivoto
$ docker compose up
```

Now point your browser to http://localhost:8080/. The "Emoji Vote" page shows up. Try it out.

Tear down the local app
```console
$ docker compose down
```

#### 3. Use the app remotely

We create the cluster resources by applying the `kustomize/deployment` directory using the following command:

```console
$ kubectl apply -k kustomize/deployment
namespace/emojivoto created
serviceaccount/emoji created
serviceaccount/voting created
serviceaccount/web created
service/emoji created
service/voting created
service/web created
deployment.apps/emoji created
deployment.apps/vote-bot created
deployment.apps/voting created
deployment.apps/web created
```

Check that the pods are up and running with:
```console
$ kubectl -n emojivoto get pod
NAME                       READY   STATUS    RESTARTS   AGE
emoji-7d8d6fb869-wp5kc     1/1     Running   0          23s
vote-bot-766b9f68b-9bsqk   1/1     Running   0          23s
voting-7d49b58d7b-n7bc8    1/1     Running   0          23s
web-7cc498695b-7dtcl       1/1     Running   0          23s
```

Connect to the cluster and verify that the web service is functional. The `telepresence serve web` will start the browser with a URL that points to the "web" service:
```console
$ telepresence connect -n emojivoto --docker
 ✔ Connected to context minikube, namespace emojivoto (https://192.168.49.2:8443)           0.8s
$ telepresence serve web
```

## Extend With "proxy"

Let's assume that we don't want to run the "voting" service locally. Instead, we want to replace it with a corresponding service that runs in the cluster. In other words, we want the local service to act as a _proxy_ for the remote service.

The original `compose.yaml` file contains this:

```yaml
services:
  web:
    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0
    environment:
      - WEB_PORT=8080
      - EMOJISVC_HOST=emoji:8080
      - VOTINGSVC_HOST=voting:8080
      - INDEX_BUNDLE=dist/index_bundle.js
    ports:
      - "8080:8080"
    depends_on:
      - voting
      - emoji

  vote-bot:
    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0
    entrypoint: emojivoto-vote-bot
    environment:
      - WEB_HOST=web:8080
    depends_on:
      - web

  emoji:
    image: ghcr.io/telepresenceio/emojivoto-emoji:0.3.0
    environment:
      - GRPC_PORT=8080
    ports:
      - "8081:8080"

  voting:
    image: ghcr.io/telepresenceio/emojivoto-voting:0.3.0
    environment:
      - GRPC_PORT=8080
      - POLL_FILE=/data/polls.json
    ports:
      - "8082:8080"
    volumes:
      - data:/data

volumes:
  data:
```

The proxy requires a Telepresence connection to the cluster, using the `emojivoto` namespace in this example. Connections are defined in a top-level `x-tele` extension, where each connection is identified by a name and configured with attributes that correspond to the flags used in the `telepresence connect` command. The top-level extension is structured as follows:

```yaml
x-tele:
  connections:
    - name: emojivoto
      namespace: emojivoto
```

To enable proxy functionality, an extension of type `proxy` is added to the `voting` service declaration:

```yaml
x-tele:
  type: proxy
  connection: emojivoto
```

The `connection: emojivoto` field specifies that the proxy uses the `emojivoto` connection defined in the top-level extension, linking it to the `emojivoto` namespace. This field is optional when the top-level extension includes only one connection. Similarly, the `name: emojivoto` in the connection declaration is optional and only required when multiple connections are defined.

The resulting file will then look like this:

```yaml
x-tele:
  connections:
    - name: emojivoto
      namespace: emojivoto
services:
  web:
    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0
    environment:
      - WEB_PORT=8080
      - EMOJISVC_HOST=emoji:8080
      - VOTINGSVC_HOST=voting:8080
      - INDEX_BUNDLE=dist/index_bundle.js
    ports:
      - "8080:8080"
    depends_on:
      - voting
      - emoji

  vote-bot:
    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0
    entrypoint: emojivoto-vote-bot
    environment:
      - WEB_HOST=web:8080
    depends_on:
      - web

  emoji:
    image: ghcr.io/telepresenceio/emojivoto-emoji:0.3.0
    environment:
      - GRPC_PORT=8080
    ports:
      - "8081:8080"

  voting:
    x-tele:
      type: proxy
      connection: emojivoto
    image: ghcr.io/telepresenceio/emojivoto-voting:0.3.0
    environment:
      - GRPC_PORT=8080
      - POLL_FILE=/data/polls.json
    ports:
      - "8082:8080"
    volumes:
      - data:/data

volumes:
  data:
```

> [!TIP]
> Instead of modifying the original `compose.yaml` file, we can add a new file adjacent to it and call it `compose.override.yaml`. Docker Compose will automatically merge this override with the `compose.yaml`. So, leave original `compose.yaml` intact, and instead add a `compose.override.yaml` file with the following contents (the optional connection name and proxy connection reference are both removed):
>
> ```yaml
> x-tele:
>   connections:
>     - namespace: emojivoto
> services:
>  voting:
>    x-tele:
>      type: proxy
> ```

### Running the Extended Sample

Running with `telepresence compose up` will discover the extension, connect to the cluster, modify an in-memory version of the Compose specification so that it no longer contains the "voting" service, alter the DNS so that lookups for this service instead find the one in the cluster, and configure routing so that the "web" service still finds the "voting" service. We can verify this using:
```console
$ telepresence compose
 ✔ Connected to context minikube, namespace emojivoto (https://192.168.49.2:8443)     2.4s 
 ✔ Proxied service voting                                                             0.0s 
[+] Running 4/4
 ✔ Network emojivoto_default        Created                                           0.1s 
 ✔ Container emojivoto-emoji-1  Created                                               0.0s 
 ✔ Container emojivoto-web-1        Created                                           0.0s 
 ✔ Container emojivoto-vote-bot-1   Created                                           0.0s 
Attaching to emoji-1, vote-bot-1, web-1
emoji-1     | 2025/07/19 05:42:39 Starting grpc server on GRPC_PORT=[8080]
web-1       | 2025/07/19 05:42:39 Connecting to [voting:8080]
web-1       | 2025/07/19 05:42:39 Connecting to [emoji:8080]
web-1       | 2025/07/19 05:42:39 Starting web server on WEB_PORT=[8080] and MESSAGE_OF_THE_DAY=[]
vote-bot-1  | ✔ Voting for :older_man:
vote-bot-1  | ✔ Voting for :100:
vote-bot-1  | ✔ Voting for :bulb:
...
```
We now see "Proxied service voting" and then, in contrast to the output from a `docker compose up`, no further output from that service. The `vote-bot-1` continues to vote though, so it's obviously still talking to a `voting`.

### Takeaways
Using our Telepresence "proxy" extension, we have now successfully modified our setup so that the services in the compose.yaml file interact with a service in the cluster.

### Proxy the web

Can we proxy the web service and still reach it using `localhost:8080` in our browser? Let's give it a try using the following `compose.override.yaml` file:

```yaml
x-tele:
  connections:
    - namespace: emojivoto
services:
  web:
    x-tele:
      type: proxy
      ports:
        - 8080:80
```

Worth noting here is that the original docker-compose service will expose port 8080, so that's what our proxy must expose to other containers. In the cluster, however, the web service uses port 80. This is why we need to specify the port mapping in the extension:
```yaml
      ports:
        - 8080:80
```

With this change, we can now run the sample using `telepresence compose up` and connect to the web service using `localhost:8080` in our browser.

## Extend With "replace"

Our previous example used a proxy to replace a local service with a remote service. In this sample we will do the opposite. We will make the remote services talk to services in our Docker Compose file. In essence, we will let the remote `web` and `vote-bot` service use the `emoji` and `vote` service that we run locally.

Our extensions look like this:
```yaml
x-tele:
  connections:
    - namespace: emojivoto
services:
  emoji:
    x-tele:
      type: replace
  voting:
    x-tele:
      type: replace
  vote-bot:
    profiles:
      - notEnabled
```

> [!NOTE]
> The last part:
> ```yaml
>   vote-bot:
>     profiles:
>       - notEnabled
> ```
> effectively disables the local `vote-bot` service so that only the vote-bot running in the cluster is active. It's optional, but it makes it easier to see what happens when we run the sample:

```console
$ telepresence compose up
 ✔ Connected to context minikube, namespace emojivoto (https://192.168.49.2:8443)     2.8s 
[+] Attaching 2/2
 ✔ emoji  Replaced service emoji                                                      2.0s 
 ✔ voting Replaced service voting                                                     1.6s 
[+] Running 4/4
 ✔ Network emojivoto_default    Created                                               0.0s 
 ✔ Container emojivoto-voting1  Created                                               0.1s 
 ✔ Container emojivoto-emoji-1  Created                                               0.1s 
 ✔ Container emojivoto-web-1    Created                                               0.1s 
Attaching to emoji-1, voting-1, web-1
emoji-1   | 2025/08/05 09:17:39 Starting prom metrics on PROM_PORT=[8801]
emoji-1   | 2025/08/05 09:17:39 Starting grpc server on GRPC_PORT=[8080]
voting-1  | 2025/08/05 09:17:39 Storing votes in file /data/polls.json
voting-1  | 2025/08/05 09:17:39 Starting prom metrics on PROM_PORT=[8801]
voting-1  | 2025/08/05 09:17:39 Starting grpc server on GRPC_PORT=[8080]
voting-1  | 2025/08/05 09:17:39 Using failureRate [0.000000] and artificialDelayDuration [0s]
web-1     | 2025/08/05 09:17:39 Connecting to [voting:8080]
web-1     | 2025/08/05 09:17:39 Connecting to [emoji:8080]
web-1     | 2025/08/05 09:17:39 Starting web server on WEB_PORT=[8080] and MESSAGE_OF_THE_DAY=[]
voting-1  | 2025/07/20 04:39:58 Voted for [:fax:], which now has a total of [12] votes
voting-1  | 2025/07/20 04:39:59 Voted for [:doughnut:], which now has a total of [231] votes
voting-1  | 2025/07/20 04:40:00 Voted for [:flight_departure:], which now has a total of [8] votes
```

We can observe that after an initial delay - caused by the remote vote bot reconnecting after the replacement of the vote container - the votes arrive, even though no vote-bot is running locally. Furthermore, if we start a browser on http://localhost:8080 now, we see the same leaderboard as a browser started using `telepresence serve web` which serves up the remote service.

In the cluster, the pods for the "voting" and "emoji" deployments have been replaced with traffic-agents that redirect all traffic to their corresponding "voting" and "emoji" Docker Compose service. We can easily verify this using:
```console
$ kubectl -n emojivoto get pod -l app=voting -o jsonpath='{.items.*.spec.containers.*.name}'
traffic-agent
$ kubectl -n emojivoto get pod -l app=emoji -o jsonpath='{.items.*.spec.containers.*.name}'
traffic-agent
```

### Remote Mounts

One interesting observation is that the vote counts don't start from zero. Instead, they are synced with the vote counts used by the voting service in the cluster. This is because the "replace" extension automatically replaced the mounted "data" volume with a remote mount of the corresponding volume in the replaced container. This default behavior can be controlled using mount policies.

#### Preventing Remote Mounts

To prevent the remote mounts from happening, and instead keep the volumes created by Docker Compose, we can add a `mounts` object to the top-level `x-tele` extension in the `compose.override.yaml` file:
```yaml
x-tele:
  connections:
    - namespace: emojivoto
  mounts:
    - volume: data
      policy: local
```

The `mounts` object is a list of objects, each with a `volume` field that corresponds to the name of the volume in the Compose file or a `volumePattern`, a regular expression that matches that name, and a `policy` field that can be set to either `local`, `ignore`, `remote` or `remoteReadOnly`. The default is to use whatever policy that the traffic-agent uses for the volume.

### Takeaways
Using our Telepresence "replace" extension, we have successfully modified our setup so that multiple services in the cluster have been replaced by services that run locally as part of our Docker Compose spec.