# Accessing Octelium Services

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/examples/security/octelium>.

Every *Workspace* is connected to the *Cluster* with its own Octelium *Session*, which lets the tools running inside it reach the Octelium *Services* that its owner is authorized to access, by name and without any credential (read more [here](https://octelium.com/docs/cordium/latest/workspaces/secretless.md)). This example walks through a development *Workspace* for the `payments` team that uses an internal HTTP API, a PostgreSQL database, a Kubernetes cluster, an SSH server and an MCP server, none of whose credentials are present in the *Workspace*.

## The Services

The following Octelium *Services* are created by a *Cluster* administrator. Each one stores its upstream credential as an Octelium *Secret* (read more about each mode [here](https://octelium.com/docs/octelium/latest/management/core/service/overview.md)):

```yaml
kind: Service
metadata:
  name: ledger-api.payments
spec:
  mode: HTTP
  config:
    upstream:
      url: https://ledger.internal.example.com
    http:
      auth:
        bearer:
          fromSecret: ledger-api-token
---
kind: Service
metadata:
  name: pg-staging.payments
spec:
  mode: POSTGRES
  port: 5432
  config:
    upstream:
      url: postgres://pg-staging.internal.example.com
    postgres:
      user: payments_dev
      database: payments
      auth:
        password:
          fromSecret: pg-staging-password
---
kind: Service
metadata:
  name: k8s-staging
spec:
  mode: KUBERNETES
  config:
    upstream:
      url: https://k8s-staging.internal.example.com:6443
    kubernetes:
      kubeconfig:
        fromSecret: k8s-staging-kubeconfig
---
kind: Service
metadata:
  name: bastion-eu
spec:
  mode: SSH
  config:
    upstream:
      url: ssh://bastion-eu.internal.example.com
    ssh:
      user: deploy
      auth:
        privateKey:
          fromSecret: bastion-eu-key
```

The members of the `payments` *Group* are then allowed to access them via a *Policy*, which also applies to their *Workspaces* since every *Workspace* run is a *Session* of its owner (read more about *Policies* [here](https://octelium.com/docs/octelium/latest/management/core/policy.md)).

## Using the Services

Inside any of the team's *Workspaces*, the *Services* are used with their standard clients:

```bash
octelium get service
```

```bash
# The internal HTTP API
curl -s http://ledger-api.payments/v1/accounts/acc_7Hq2/balance | jq

# The staging database
psql -h pg-staging.payments -c "SELECT count(*) FROM transfers WHERE status = 'pending';"

# The staging Kubernetes cluster
octelium cfg k8s-staging
export KUBECONFIG="$HOME/.kube/k8s-staging.$OCTELIUM_DOMAIN"
kubectl -n payments get pods

# The bastion host
ssh bastion-eu
```

Since `psql` expects a database user and Octelium injects the real one, any user name works on the client side. Applications use the same addresses, for example via `DATABASE_URL=postgres://pg-staging.payments:5432/payments` and `LEDGER_API_URL=http://ledger-api.payments`.

## Using the Services in Tasks

Lifecycle tasks that access Octelium *Services* must be `POST_START` tasks, since the *Workspace* connects to the *Cluster* at the beginning of the `POST_START` phase, and they should wait until the connection is established (read more [here](https://octelium.com/docs/cordium/latest/workspaces/runtime.md#task-order)). Here is a *Template* that refreshes a local copy of reference data from the staging database every time the *Workspace* starts:

```yaml
spec:
  image:
    registry:
      url: postgres:18
  runtime:
    tasks:
      - name: sync-reference-data
        type: POST_START
        workingDir: /workspace
        onFailure: ON_FAILURE_ABORT
        run: |
          timeout 120 sh -c 'until getent hosts pg-staging.payments > /dev/null; do sleep 2; done'
          pg_dump -h pg-staging.payments -t currencies -t fee_schedules -Fc -f /workspace/reference.dump payments
```

## Connecting AI Agents to MCP Servers

MCP servers can be exposed as Octelium `MCP` *Services* too, which gives AI agents running in *Workspaces* access to tools such as issue trackers, observability backends or internal knowledge bases, again without any token in the *Workspace* (read more about MCP *Services* [here](https://octelium.com/docs/octelium/latest/management/core/service/mcp/overview.md)). Here is an `MCP` *Service* for a remote MCP server that requires a bearer token:

```yaml
kind: Service
metadata:
  name: linear
spec:
  mode: MCP
  config:
    upstream:
      url: https://mcp.linear.app/mcp
    mcp:
      endpoint: /mcp
      auth:
        bearer:
          fromSecret: linear-api-key
```

And here is how Claude Code is configured to use it from inside a *Workspace*:

```bash
claude mcp add --transport http linear http://linear/mcp
```

## Auditing

Every request made through these *Services*, including the HTTP paths, SQL queries, Kubernetes API calls, SSH sessions and MCP tool calls, is logged by Octelium with the identity of the *User* and of the *Workspace* that made it (read more [here](https://octelium.com/docs/octelium/latest/management/core/visibility.md)). To restrict what the *Workspaces* can do further, for example by allowing a *Template*'s *Workspaces* only read-only queries, read [this guide](https://octelium.com/docs/cordium/latest/workspaces/secretless.md#policies-for-workspaces).
