# Automatically Register Resources with Teleport

You can use Teleport's API to automatically register resources in your infrastructure with your Teleport cluster.

Teleport already supports the automatic discovery of [Kubernetes clusters](https://goteleport.com/docs/enroll-resources/auto-discovery/kubernetes.md) in AWS, Azure, and Google Cloud, as well as [servers](https://goteleport.com/docs/enroll-resources/auto-discovery/servers/ec2-discovery.md) on Amazon EC2. To support other resources and cloud providers, you can use the API to write your own workflow.

In this guide, we will demonstrate some libraries you can use to automatically register resources with Teleport. We will use an example you can run locally on your workstation.

## How it works

Automatic registration consists of the following steps:

- Look up resources in your infrastructure from a service discovery solution, e.g., the Kubernetes API server, Consul, or your cloud provider's APIs.
- Use the Teleport gRPC API to look up resources registered with Teleport.
- For any resources in your infrastructure that are not registered with Teleport, use the Teleport API to spin up a new Teleport service or register the resource with an existing Teleport service.
- For any resources that are registered with Teleport but not in your infrastructure, use the Teleport API to deregister the resource and, if necessary, remove the Teleport service proxying the resource.

---

DANGER

The program we build in this guide is intended as a learning tool. **Do not connect it to your production Teleport cluster.** Use a demo cluster instead.

---

## Prerequisites

- A running Teleport cluster. If you want to get started with Teleport, [sign up](https://goteleport.com/signup) for a free trial or [set up a demo environment](https://goteleport.com/docs/get-started/deploy-community.md).

- The `tctl` and `tsh` clients.

  Installing `tctl` and `tsh` clients

  1. Determine the version of your Teleport cluster. The `tctl` and `tsh` clients must be at most one major version behind your Teleport cluster version. Send a GET request to the Proxy Service at `/v1/webapi/find` and use a JSON query tool to obtain your cluster version. Replace teleport.example.com:443 with the web address of your Teleport Proxy Service:

     ```
     $ TELEPORT_DOMAIN=teleport.example.com:443
     $ TELEPORT_VERSION="$(curl -s https://$TELEPORT_DOMAIN/v1/webapi/find | jq -r '.server_version')"
     ```

  2. Follow the instructions for your platform to install `tctl` and `tsh` clients:

     **Mac**

     Download the signed macOS .pkg installer for Teleport, which includes the `tctl` and `tsh` clients:

     ```
     $ curl -O https://cdn.teleport.dev/teleport-${TELEPORT_VERSION?}.pkg
     ```

     In Finder double-click the `pkg` file to begin installation.

     ---

     DANGER

     Using Homebrew to install Teleport is not supported. The Teleport package in Homebrew is not maintained by Teleport and we can't guarantee its reliability or security.

     ---

     **Windows - Powershell**

     ```
     $ curl.exe -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION?}-windows-amd64-bin.zip
     Unzip the archive and move the `tctl` and `tsh` clients to your %PATH%
     NOTE: Do not place the `tctl` and `tsh` clients in the System32 directory, as this can cause issues when using WinSCP.
     Use %SystemRoot% (C:\Windows) or %USERPROFILE% (C:\Users\<username>) instead.
     ```

     **Linux**

     All of the Teleport binaries in Linux installations include the `tctl` and `tsh` clients. For more options (including RPM/DEB packages and downloads for i386/ARM/ARM64) see our [installation page](https://goteleport.com/docs/installation.md).

     ```
     $ curl -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ tar -xzf teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ cd teleport
     $ sudo ./install
     Teleport binaries have been copied to /usr/local/bin
     ```

* Docker installed on your workstation. [Get Started With Docker](https://www.docker.com/get-started).
* Go version 1.25.9 or above installed on your workstation. See the [Go download page](https://go.dev/dl/). You will not need to be familiar with Go to complete this guide, though Go knowledge is required if you want to build a production-ready Teleport client application.

---

TIP

Even if you do not plan to set up the demo project, you can follow this guide to see which libraries, types, and functions you can use to automatically register services with your Teleport cluster.

---

## Step 1/5. Set up your Go project

Download the source code for our minimal API client:

```
$ git clone https://github.com/gravitational/teleport -b branch/v19
$ cd teleport/examples/service-discovery-api-client
```

For the rest of this guide, we will show you how to set up this API client and explore the way it uses Teleport's API to synchronize Teleport Application Service instances with an external service discovery solution.

## Step 2/5. Define RBAC resources

Clients that communicate with Teleport's gRPC API assume the identity of a Teleport user and role in order to authenticate to the Auth Service, which authorizes the identity to perform certain API actions.

Your own Teleport user also requires permissions to impersonate the API client's user in order to generate credentials for the client.

### Create a user and role for the client application

Our client application will authenticate to Teleport as a user with permissions to create join tokens, list applications, and delete records of Application Service instances registered with the Auth Service. The user will also have permissions to access applications with any label (`app_labels`), which is necessary for listing applications.

Define a user and role with appropriate permissions for the client application by adding the following content to a file called `register-apps.yaml`:

```
kind: role
version: v5
metadata:
  name: register-apps
spec:
  allow:
    app_labels:
      '*': '*'
    rules:
      - resources: ['token']
        verbs: ['create']
      - resources: ['app']
        verbs: ['list']
      - resources: ['app_server']
        verbs: ['delete']
---
kind: user
metadata:
  name: register-apps
spec:
  roles: ['register-apps']
version: v2

```

Create the user and role:

```
$ tctl create -f register-apps.yaml
role 'register-apps' has been created
user "register-apps" has been created
```

---

TIP

You can also create and edit roles using the Web UI. Go to **Access -> Roles** and click **Create New Role** or pick an existing role to edit.

---

### Enable impersonation of the client application

As with all Teleport users, the Teleport Auth Service authenticates the `register-apps` user by issuing short-lived TLS credentials. In this case, we will request the credentials manually by *impersonating* the `register-apps` role and user.

If you are running a self-hosted Teleport Enterprise deployment and are using `tctl` from the Auth Service host, you will already have impersonation privileges.

To grant your user impersonation privileges for `register-apps`, create a file called `register-apps-impersonator.yaml` defining a role:

```
kind: role
version: v5
metadata:
  name: register-apps-impersonator
spec:
  allow:
    impersonate:
      roles:
      - register-apps
      users:
      - register-apps

```

Create the `register-apps-impersonator` role:

```
$ tctl create -f register-apps-impersonator.yaml
role 'register-apps-impersonator' has been created
```

Assign the `register-apps-impersonator` role to your Teleport user by running the appropriate commands for your authentication provider:

**Local User**

1. Retrieve your local user's roles as a comma-separated list:

   ```
   $ ROLES=$(tsh status -f json | jq -r '.active.roles | join(",")')
   ```

2. Edit your local user to add the new role:

   ```
   $ tctl users update $(tsh status -f json | jq -r '.active.username') \
     --set-roles "${ROLES?},register-apps-impersonator"
   ```

3. Sign out of the Teleport cluster and sign in again to assume the new role.

**GitHub**

1. Open your `github` authentication connector in a text editor:

   ```
   $ tctl edit github/github
   ```

2. Edit the `github` connector, adding `register-apps-impersonator` to the `teams_to_roles` section.

   The team you should map to this role depends on how you have designed your organization's role-based access controls (RBAC). However, the team must include your user account and should be the smallest team possible within your organization.

   Here is an example:

   ```
     teams_to_roles:
       - organization: octocats
         team: admins
         roles:
           - access
   +       - register-apps-impersonator

   ```

3. Apply your changes by saving and closing the file in your editor.

4. Sign out of the Teleport cluster and sign in again to assume the new role.

**SAML**

1. Retrieve your `saml` configuration resource:

   ```
   $ tctl get --with-secrets saml/mysaml > saml.yaml
   ```

   Note that the `--with-secrets` flag adds the value of `spec.signing_key_pair.private_key` to the `saml.yaml` file. Because this key contains a sensitive value, you should remove the saml.yaml file immediately after updating the resource.

2. Edit `saml.yaml`, adding `register-apps-impersonator` to the `attributes_to_roles` section.

   The attribute you should map to this role depends on how you have designed your organization's role-based access controls (RBAC). However, the group must include your user account and should be the smallest group possible within your organization.

   Here is an example:

   ```
     attributes_to_roles:
       - name: "groups"
         value: "my-group"
         roles:
           - access
   +       - register-apps-impersonator

   ```

3. Apply your changes:

   ```
   $ tctl create -f saml.yaml
   ```

4. Sign out of the Teleport cluster and sign in again to assume the new role.

**OIDC**

1. Retrieve your `oidc` configuration resource:

   ```
   $ tctl get oidc/myoidc --with-secrets > oidc.yaml
   ```

   Note that the `--with-secrets` flag adds the value of `spec.signing_key_pair.private_key` to the `oidc.yaml` file. Because this key contains a sensitive value, you should remove the oidc.yaml file immediately after updating the resource.

2. Edit `oidc.yaml`, adding `register-apps-impersonator` to the `claims_to_roles` section.

   The claim you should map to this role depends on how you have designed your organization's role-based access controls (RBAC). However, the group must include your user account and should be the smallest group possible within your organization.

   Here is an example:

   ```
     claims_to_roles:
       - name: "groups"
         value: "my-group"
         roles:
           - access
   +       - register-apps-impersonator

   ```

3. Apply your changes:

   ```
   $ tctl create -f oidc.yaml
   ```

4. Sign out of the Teleport cluster and sign in again to assume the new role.

You will now be able to generate signed certificates for the `register-apps` role and user.

## Step 3/5. Export an identity for the client application

Like all Teleport users, `register-apps` needs signed credentials in order to connect to your Teleport cluster. You will use the `tctl auth sign` command to request these credentials for your plugin.

The following `tctl auth sign` command impersonates the `register-apps` user, generates signed credentials, and writes an identity file to the local directory:

```
$ tctl auth sign --user=register-apps --out=auth.pem
```

The identity file, `auth.pem`, includes both TLS and SSH credentials. Your client application uses the SSH credentials to connect to the Proxy Service, which establishes a reverse tunnel connection to the Auth Service. The plugin uses this reverse tunnel, along with your TLS credentials, to connect to the Auth Service's gRPC endpoint.

## Step 4/5. Write the client application

In this step, we will walk you through the example client application.

Our demo application watches for containers running RabbitMQ, a popular open source message broker, in order to register (and, if necessary, deregister) its management API with Teleport. It does so by:

- Fetching RabbitMQ management API endpoints registered with Teleport.
- Fetching RabbitMQ containers.
- If a RabbitMQ container does not correspond to a management API endpoint, register the container's management API endpoint with Teleport by creating a join token and running a new Teleport Application Service container.
- If a RabbitMQ API endpoint registered with Teleport does not correspond to a RabbitMQ container, delete the corresponding Application Service container and deregister the RabbitMQ API endpoint.

---

DYNAMIC REGISTRATION

While the client application launches a new Application Service instance to proxy every instance of the target application, you can also proxy multiple applications through the same Application Service instance.

To do so, you would write the client application to register applications dynamically via the Teleport API. We will discuss ways to do so in this guide.

---

### Imports

The program, which you can find in `examples/service-discovery-api-client/main.go`, imports the following packages:

| Package        | Description                                                                                                                                                                                                                                               |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `context`      | Includes the `context.Context` type. `context.Context` is an abstraction for controlling long-running routines, such as connections to external services, that might fail or time out. Programs can cancel contexts or assign them timeouts and metadata. |
| `crypto/rand`  | Includes cryptographic randomization functions, which we will use to generate join tokens for Application Services to use to establish trust with the Teleport Auth Service.                                                                              |
| `encoding/hex` | The number generator we use from `crypto/rand` returns data in bytes, so we will encode these as hexadecimal strings using this package.                                                                                                                  |
| `fmt`          | Formats data for printing, strings, or errors.                                                                                                                                                                                                            |
| `net`          | Deals with network I/O.                                                                                                                                                                                                                                   |
| `net/url`      | Parses URLs.                                                                                                                                                                                                                                              |
| `strings`      | Manipulates strings.                                                                                                                                                                                                                                      |
| `time`         | Deals with time. We will use this to define a timeout for connecting to the Auth Service along with a ticker for executing our discovery logic in a loop.                                                                                                 |

The client imports the following third-party code:

| Package                                        | Description                                                                                                                                                                                     |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `github.com/docker/docker/api/types`           | Types used in the Docker daemon API. Aliased as `dtypes` here.                                                                                                                                  |
| `github.com/docker/docker/api/types/container` | Container-specific types used in the Docker daemon API.                                                                                                                                         |
| `github.com/docker/docker/api/types/filters`   | Types used for filtering containers in the Docker daemon API.                                                                                                                                   |
| `github.com/docker/docker/api/types/strslice`  | A utility package for working with slices of strings in Docker's API client library. (In Go, slices are similar to arrays, but with variable lengths and capacities. Arrays have a fixed size.) |
| `github.com/docker/docker/client`              | The Docker API client library, aliased as `docker` here.                                                                                                                                        |
| `github.com/gravitational/teleport/api/client` | A library for authenticating to the Auth Service's gRPC API and making requests, aliased as `teleport`.                                                                                         |
| `github.com/gravitational/api/client/proto`    | Teleport's protocol buffer API specification.                                                                                                                                                   |
| `github.com/gravitational/teleport/api/types`  | Types used in the Auth Service API, e.g., Application Service records.                                                                                                                          |
| `github.com/gravitational/trace`               | Presents errors with more useful detail than the standard library provides.                                                                                                                     |
| `google.golang.org/grpc`                       | The gRPC client and server library.                                                                                                                                                             |

### Global declarations

The program defines constants in a visible location so, later on, it's easier to make them configurable:

```
const (
	// Assign proxyAddr to the host and port of your Teleport Proxy Service instance
	proxyAddr      string = ""
	teleportImage  string = "public.ecr.aws/gravitational/teleport-distroless:13.3.7"
	initTimeout           = time.Duration(30) * time.Second
	updateInterval        = time.Duration(5) * time.Second
	tokenTTL              = time.Duration(5) * time.Minute
	networkName    string = "bridge"
	managementPort string = "15672"
	tokenLenBytes         = 16
	rabbitMQImage string  = "rabbitmq:3-management"
)

```

We will use these constants later in the program. They define some values we may want to change later, including:

| Constant         | Description                                                                                                                                                                                                                                                                                                   |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `proxyAddr`      | The host and port of the Teleport Proxy Service, e.g., `mytenant.teleport.sh:443`, which we will use to connect the client to your cluster. **Assign this to your own Proxy Service's host and port.**                                                                                                        |
| `teleportImage`  | The name of the Teleport container image, which the program will use to run instances of the Teleport Application Service.                                                                                                                                                                                    |
| `initTimeout`    | The timeout for connecting to the Teleport cluster (30 seconds).                                                                                                                                                                                                                                              |
| `updateInterval` | How often the program will wait between reconciling Application Service instances and application containers (5 seconds).                                                                                                                                                                                     |
| `tokenTTL`       | How long of a TTL to set for join tokens that Application Service instances will use to establish trust with the Teleport cluster. This client application will use join tokens immediately after creating them, so we can set this TTL to a small value (5 minutes) to prevent this credential from leaking. |
| `networkName`    | The name of the Docker network to search for application containers. `bridge` is the default local network managed by the Docker daemon.                                                                                                                                                                      |
| `managementPort` | The management API port of our RabbitMQ containers.                                                                                                                                                                                                                                                           |
| `tokenLenBytes`  | The length of join tokens to create, in bytes.                                                                                                                                                                                                                                                                |
| `rabbitMQImage`  | The name of the RabbitMQ container image. The container image we use here has the management API enabled.                                                                                                                                                                                                     |

Below the `const` declaration is the following type declaration:

```
type tokenDemoApp struct {
	dockerClient   *docker.Client
	teleportClient *teleport.Client
}

```

This is the only type we will declare in the program. It includes pointers to clients for the Docker daemon API and Teleport Auth Service API. This program will initialize the clients, then call methods of `tokenDemoApp`.

### Get the management endpoint URLs of registered applications

Our program fetches the applications that are registered with Teleport. Later, it will compare these applications with the currently running application containers.

Teleport represents registered resources in two ways:

- **Dynamic resources:** These are configuration documents that you have applied against your cluster, e.g., `app`, `kube_cluster`, and `db` resources. Teleport automatically finds an instance of the appropriate service to proxy these.
- **Service instances:** These are Teleport services proxying specific resources in your infrastructure, which you specify in the service's configuration file.

In our application, we're creating one Application Service instance per app, so we'll list Application Service instances and examine the resources they are proxying. Other client applications may need to look up dynamically registered resources instead.

Here is the method that the application uses to fetch the URLs of registered applications:

```
func (t *tokenDemoApp) listRegisteredAppURLs(ctx context.Context) (map[string]types.AppServer, error) {
	m := make(map[string]types.AppServer)

	for {
		req := proto.ListResourcesRequest{
			ResourceType: "app_server",
			Limit:        10,
		}
		resp, err := t.teleportClient.ListResources(
			ctx,
			req,
		)

		if err != nil {
			return nil, trace.Wrap(err)
		}

		for _, r := range resp.Resources {
			if p, ok := r.(types.AppServer); ok {
				m[p.GetApp().GetURI()] = p
			}
		}

		// No more pages to request
		if resp.NextKey == "" {
			break
		}

		req.StartKey = resp.NextKey
	}

	return m, nil
}

```

`tokenDemoApp.teleportClient` is the `*Client` type from Teleport's API library. The `ListResources` method of the `*Client` type queries the Teleport API for a list of resources with the parameters named in a `proto.ListResourcesRequest`.

Since results are paginated, `listRegisteredAppURLs` executes this query in a `for` loop. As long as the query's response has a nonempty `NextKey` field, indicating the key to use to look up the start of the next page, the function executes the query again using the next key.

Eventually, we will expand this program to reconcile registered applications with application containers by comparing two maps (i.e., hash tables):

- A map of URL strings to `types.AppServer`s, representing the applications registered with Teleport.
- A map where keys are URLs of management API endpoints for our RabbitMQ containers.

The `listRegisteredAppURLs` function generates the first map by iterating through the result of `ListResources` and, for each resources that is a `types.AppServer`, inserts the URL of its proxied application as a key within the map, assigning the corresponding `types.AppServer` as its value.

In the next section, we will show you how to generate the second map.

Other ways to get resources

`ListResources` is a general-purpose method for fetching resources, and supports sorting and filtering results. Depending on the needs of your client application, you can consider a resource-specific method instead.

For example, this method returns only dynamically registered applications:

```
func (c *Client) GetApps(ctx context.Context) ([]types.Application, error)

```

This method returns Kubernetes Service instances:

```
func (c *Client) GetKubernetesServers(ctx context.Context) ([]types.KubeServer, error)

```

In general, `*Client` methods following the pattern `Get[A-Za-z]+` retrieve dynamically registered resources, while methods following the pattern `Get[A-Za-z]+(Servers|Services)` retrieve records of Teleport services.

### Get the management endpoint URLs of application containers

The next function fetches the URLs of our RabbitMQ containers:

```
func (t *tokenDemoApp) listAppContainerURLs(ctx context.Context, image string) (map[string]struct{}, error) {
	c, err := t.dockerClient.ContainerList(ctx, dtypes.ContainerListOptions{
		Filters: filters.NewArgs(filters.KeyValuePair{
			Key:   "ancestor",
			Value: image,
		}),
	})
	if err != nil {
		return nil, trace.Wrap(err)
	}

	l := make(map[string]struct{})

	for _, r := range c {
		b, ok := r.NetworkSettings.Networks[networkName]
		// Not connected to the chosen network, so skip it
		if !ok {
			continue
		}

		u, err := url.Parse("http://"+net.JoinHostPort(
			b.IPAddress,
			managementPort,
		))

		if err != nil {
			return nil, trace.Wrap(err)
		}

		l[u.String()] = struct{}{}
	}

	return l, nil
}

```

This function uses the `tokenDemoApp.dockerClient` field, the Docker API client library's `Client` type, to send the Docker daemon a request to list containers. The `dtypes.ContainerListOptions` struct instructs the Docker daemon to list only the containers that have the image we specify in the `image` parameter.

For each container returned by the Docker daemon, we look up the container's IP address within a predetermined network (the default bridge network). We know in advance that all RabbitMQ containers will have the management port opened, so we use the container's IP address and the management port to compose a URL and insert it into the map that we will return from this function.

Note that the map this function returns assigns URL strings to empty structs. In Go, the empty struct consumes no memory. Go programs often use the empty struct for the values of hash tables, since a program can search for a key in constant time without using the value.

### Create a token for a new Application Service instance

After reconciling the map of registered applications with the map of application containers, we will need to:

- Launch Application Service instances to proxy unregistered application containers.
- Delete Application Service instances that no longer correspond to a running application.

To launch a new Application Service instance, we will create a **join token**. Teleport's services can join a cluster by presenting a token to the Auth Service.

The code below generates a join token for a new Application Service instance:

```
func cryptoRandomHex(len int) (string, error) {
	randomBytes := make([]byte, len)
	if _, err := rand.Read(randomBytes); err != nil {
		return "", trace.Wrap(err)
	}
	return hex.EncodeToString(randomBytes), nil
}

func (t *tokenDemoApp) createAppToken(ctx context.Context) (string, error) {
	n, err := cryptoRandomHex(tokenLenBytes)
	if err != nil {
		return "", trace.Wrap(err)
	}

	tok, err := types.NewProvisionTokenFromSpec(
		n,
		time.Now().Add(tokenTTL),
		types.ProvisionTokenSpecV2{
			Roles: types.SystemRoles{types.RoleApp},
		})

	if err := t.teleportClient.CreateToken(ctx, tok); err != nil {
		return "", trace.Wrap(err)
	}
	return n, nil
}

```

The example above demonstrates how to use `*client.Client.CreateToken` to join a resource to a Teleport cluster using a token. If you have an instance of a Teleport service running already (e.g., the Application Service), it is simpler to join the resource to the cluster dynamically using a method similar to `*client.Client.CreateApp`.

Registering applications dynamically

To register applications dynamically, rather than start a new Application Service instance to proxy an application, use the `(*client.Client).CreateApp` method:

```
func (c *Client) CreateApp(ctx context.Context, app types.Application) error

```

For this to work, you must have an instance of the Application Service already running. Your API client application's Teleport user must also have the following permissions:

```
spec:
  allow:
    rules:
      - resources: ['app']
        verbs: ['create']

```

For other resources, use the following methods, which require their own corresponding role permissions with the `create` verb:

| Resource           | Method                                   | Within a Role     |
| ------------------ | ---------------------------------------- | ----------------- |
| Database           | `*client.Client.CreateDatabase`          | `db`              |
| Kubernetes cluster | `*client.Client.CreateKubernetesCluster` | `kube_cluster`    |
| Windows Desktop    | `*client.Client.CreateWindowsDesktop`    | `windows_desktop` |

Since a server must run an instance of the Teleport Service in order to join a Teleport cluster, API clients can only register servers by using tokens.

`cryptoRandomHex` is a copy of a function that Teleport defines internally. It uses the `crypto/rand` package to generate random bytes, then converts them to a string in hexadecimal format, which we will use for the join token. You can use any secure cryptographic technique to generate a join token.

`*tokenDemoApp.createAppToken` calls `cryptoRandomHex` and uses the result by calling `types.NewProvisionTokenFromSpec`. This returns a specification for a join token that we will use when sending a request to the Teleport API, which we do via `t.teleportClient.CreateToken`.

In this case, we assign our token the TTL we configured earlier, plus the `types.RoleApp` role. This indicates to the Auth Service that the token is for an Application Service instance.

`CreateToken` returns an error if token creation fails. If it succeeds, we can return the token to the caller of `*tokenDemoApp.createAppToken` so it can run a new Application Service instance with the token.

Looking up tokens

Client applications can look up tokens they have created by calling the following function:

```
func (c *Client) GetTokens(ctx context.Context) ([]types.ProvisionToken, error)

```

To do this, your client application will need the following permissions in its Teleport role:

```
spec:
  allow:
    rules:
      - resources: ['token']
        verbs: ['list', 'read']

```

In the application we are demonstrating in this guide, there is no need to look up tokens, since the application already knows the token it created.

### Start an Application Service container

Our program needs a way to launch Application Service instances. To do this, we will use the token we created earlier, plus the URL of a RabbitMQ management API endpoint, to launch a container:

```
func (t *tokenDemoApp) startApplicationServiceContainer(
	ctx context.Context,
	token string,
	u url.URL,
) error {

	name := strings.ReplaceAll(u.Hostname(), ".", "-")
	resp, err := t.dockerClient.ContainerCreate(
		ctx,
		&container.Config{
			Image: teleportImage,
			Entrypoint: strslice.StrSlice{
				"/usr/bin/dumb-init",
				"teleport",
				"start",
				"--roles=app",
				"--auth-server=" + proxyAddr,
				"--token=" + token,
				"--app-name=rabbitmq-" + name,
				"--app-uri=" + u.String(),
			},
		},
		nil,
		nil,
		nil,
		"",
	)
	if err != nil {
		return trace.Wrap(err)
	}

	err = t.dockerClient.ContainerStart(
		ctx,
		resp.ID,
		dtypes.ContainerStartOptions{},
	)
	if err != nil {
		return trace.Wrap(err)
	}

	return nil
}

```

The Teleport Application Service redirects traffic to a subdomain of your Teleport Web UI address to a registered application. We need a name for the application that is URL safe but does not clash with other registered applications. In this case, we use the IP addresses of our RabbitMQ containers, with dots replaced by hyphens.

Next, we create the container. The executable that serves as the container's entrypoint is the same one that the `teleport` image uses by default, but with additional flags that launch the container as an Application Service instance and set it to proxy our RabbitMQ container.

Finally, we use the ID of the container we created to run the container.

### Remove an Application Service instance

Alongside creating Application Service containers, our client application will reconcile registered applications with running containers by removing unnecessary Application Service instances:

```
func (t *tokenDemoApp) pruneAppServiceInstance(ctx context.Context, p types.AppServer) error {
	host := p.GetHostname()

	if err := t.teleportClient.DeleteApplicationServer(
		ctx,
		p.GetNamespace(),
		p.GetHostID(),
		p.GetName(),
	); err != nil {
		return trace.Wrap(err)
	}

	fmt.Println("Deleted unnecessary Application Service record:", p.GetName())

	// Don't check errors when removing the container, since it may already
	// have been removed.
	t.dockerClient.ContainerStop(ctx, host, container.StopOptions{})
	t.dockerClient.ContainerRemove(ctx, host, dtypes.ContainerRemoveOptions{})

	fmt.Println("Deleted unnecessary Application Service container:", host)
	return nil
}

```

This function takes a `types.AppServer`, deregisters it from Teleport, and removes its associated Application Service container.

While Teleport deregisters stale Application Service records automatically, this can take some time after stopping an Application Service instance.

---

CHANGING A RESOURCE'S TTL

You can change the interval that Teleport will use to check the availability of a resource before de-registering it from its backend. To do so, use the `SetExpiry` method of the `types.Resource` interface.

For example, the following `SetExpiry` call configures a `WindowsDesktopV3` resource to expire in 10 minutes unless Teleport confirms its availability:

```
desktop.SetExpiry(time.Now().Add(10 * time.Minute))

```

---

To deregister the Application Service instance manually, we call the `*Client.DeleteApplicationServer` method, using the `p` parameter of the `pruneAppServiceInstance` function to get the namespace, host ID, and name of the Application Service instance to delete.

---

TIP

While Teleport namespaces are deprecated, they still appear occasionally in the Teleport API client library. The only namespace that Teleport supports is called `default`.

---

Next, this function stops and removes the Application Service container associated with the `types.AppServer` we want to prune. The hostname of an Application Service record is the same as the ID of the Application Service container, so we can pass the hostname to `t.dockerClient.ContainerStop` and `t.dockerClient.ContainerRemove`.

### Reconcile application containers with registered applications

We have declared a number of functions to list, add, and remove Application Service instances and application containers. Next, we will use these functions to implement our reconciliation logic:

```
func (t *tokenDemoApp) reconcileApps() error {
	ctx := context.Background()
	apps, err := t.listRegisteredAppURLs(ctx)
	if err != nil {
		return trace.Wrap(err)
	}

	urls, err := t.listAppContainerURLs(ctx, rabbitMQImage)
	if err != nil {
		return trace.Wrap(err)
	}

	for u, _ := range urls {
		if _, ok := apps[u]; ok {
			continue
		}
		tok, err := t.createAppToken(ctx)
		if err != nil {
			return trace.Wrap(err)
		}
		fmt.Println("Created a new application token for URL: " + u)

		r, err := url.Parse(u)
		if err != nil {
			return trace.Wrap(err)
		}

		err = t.startApplicationServiceContainer(ctx, tok, *r)
		if err != nil {
			return trace.Wrap(err)
		}
		fmt.Println("Started an Application Service container to proxy URL: " + u)
	}

	for a, p := range apps {
		_, ok := urls[a]
		if ok {
			continue
		}

		if err := t.pruneAppServiceInstance(ctx, p); err != nil {
			return trace.Wrap(err)
		}
	}
	return nil
}

```

`*tokenDemoApp.reconcileApps` works by calling `listAppContainerURLs` and `listRegisteredAppURLs` to generate maps of registered applications and running application containers. It then iterates through the URLs within each map's keys, checking for the presence of the URL in one map within the other map.

If a URL within the map of application containers is not present within the map of registered applications, it means that one application is not yet registered, so we generate a token and start a Teleport Application Service instance.

If a URL within the map of registered applications is not present within the map of application containers, it means that there is an unnecessary Application Service instance, and we call `pruneAppServiceInstance` to remove it.

### Initialize clients

The `*tokenDemoApp.reconcileApps` method performs a single reconciliation. The next step is to initialize our API clients so we can run the reconciliation in a loop within the entrypoint of our program:

```
func newTokenDemoApp() *tokenDemoApp {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, initTimeout)
	defer cancel()
	creds := teleport.LoadIdentityFile("auth.pem")

	t, err := teleport.New(ctx, teleport.Config{
		Addrs:       []string{proxyAddr},
		Credentials: []teleport.Credentials{creds},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println("Connected to Teleport")

	d, err := docker.NewClientWithOpts(
		docker.WithAPIVersionNegotiation(),
	)
	if err != nil {
		panic(err)
	}
	fmt.Println("Connected to the Docker daemon")

	return &tokenDemoApp{
		teleportClient: t,
		dockerClient:   d,
	}

}

```

`client`, which we alias as `teleport` here, is Teleport's library for setting up an API client. Our plugin initializes a Teleport client by calling `client.LoadIdentityFile` to obtain a `client.Credentials`. It then uses the `client.Credentials` to call `client.New`, which connects to the Teleport Proxy Service specified in the `Addrs` field using the provided identity file.

---

WARNING

This program does not validate your credentials or Teleport cluster address. Make sure that:

- The identity file you exported earlier does not have an expired TTL
- The value you supplied to the `Addrs` field in `teleport.Config` includes both the host **and** the web port of your Teleport Proxy Service, e.g., `mytenant.teleport.sh:443`

---

The `newTokenDemoApp` function also initializes a Docker client. It uses version negotiation (`docker.WithAPIVersionNegotiation`) to avoid errors due to mismatches between your client's API version and the Docker daemon's.

### The entrypoint

We tie our application together with the entrypoint function, `main`:

```
func main() {
	fmt.Println("Starting the application")
	app := newTokenDemoApp()

	k := time.NewTicker(updateInterval)
	defer k.Stop()
	for {
		<-k.C
		if err := app.reconcileApps(); err != nil {
			panic(err)
		}
	}
}

```

The `main` function calls `newTokenDemoApp` to initialize our API clients. It then calls `time.NewTicker`, which returns a Go **channel** that the entrypoint will use to run the reconciliation routine. A channel is a Go primitive that manages communication between concurrent routines.

After we create a ticker, the entrypoint receives from the ticker's channel (`k.C`) whenever the `updateInterval` elapses. It blocks until it receives from the channel, calling `app.reconcileApps` every interval.

## Step 5/5. Test your client application

Run the client application to see how Teleport can register and deregister resources in your infrastructure to synchronize with your service discovery solution.

Make sure you have pulled the Teleport container image so the application can create containers based on it:

```
$ docker image pull public.ecr.aws/gravitational/teleport-distroless:13.3.7
```

In your project directory, run the following command:

```
$ go run main.go
Starting the application
Connected to Teleport
Connected to the Docker daemon
```

In a new terminal, run three new RabbitMQ containers:

```
$ for i in {1..3}; do docker run -d rabbitmq:3-management; done;
```

The terminal where you ran the application should show output similar to the following:

```
Created a new application token for URL: http://172.17.0.4:15672
Started an Application Service container to proxy URL: http://172.17.0.4:15672
Created a new application token for URL: http://172.17.0.3:15672
Started an Application Service container to proxy URL: http://172.17.0.3:15672
Created a new application token for URL: http://172.17.0.2:15672
Started an Application Service container to proxy URL: http://172.17.0.2:15672

```

Verify that the RabbitMQ instances are registered with Teleport:

```
$ tsh apps ls
Application         Description Type Public Address           Labels
------------------- ----------- ---- ------------------------ -------------------
rabbitmq-172-17-0-2             HTTP rabbitmq-172-17-0-2.3... teleport.dev/origin
rabbitmq-172-17-0-3             HTTP rabbitmq-172-17-0-3.3... teleport.dev/origin
rabbitmq-172-17-0-4             HTTP rabbitmq-172-17-0-4.3... teleport.dev/origin
```

Next, stop one of your RabbitMQ containers:

```
$ docker stop $(docker ps --filter "ancestor=rabbitmq:3-management" -q --last 1)
```

The terminal where you ran the application should show output similar to the following:

```
Deleted unnecessary Application Service record: rabbitmq-172-17-0-4
Deleted unnecessary Application Service container: 63facaa3033a

```

You should now see two registered applications when you run `tsh apps ls`.

## Next steps

We have implemented a Teleport API client that keeps registered applications up to date with our running Docker containers. You can use Teleport's API to automatically sync your registered Teleport resources with your own service discovery solution.

### Consult examples

Teleport includes its own automatic resource discovery solution, the Teleport Discovery Service, and you can [consult its source](https://github.com/gravitational/teleport/tree/v19.0.0-dev/lib/srv/discovery) to see how Teleport implements its discovery logic.

The Teleport code repository contains [examples of production-ready Teleport API clients](https://github.com/gravitational/teleport/tree/v19.0.0-dev/examples/). While we currently do not maintain plugins that auto-discover resources, you can use these examples to see how to implement configuration parsing, retries, and other tasks.

### Provision the client application with short-lived credentials

In this example, we used the `tctl auth sign` command to fetch credentials for the program you wrote. For production usage, we recommend provisioning short-lived credentials via Machine & Workload Identity, which reduces the risk of these credentials becoming stolen. View our [Machine & Workload Identity documentation](https://goteleport.com/docs/machine-workload-identity/introduction.md) to learn more.
