# Running Containers

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

*Workspaces* can run their own containers via rootless Podman, which turns a *Workspace* into a complete environment for applications that depend on databases, caches, message brokers and other services (read more [here](https://octelium.com/docs/cordium/latest/workspaces/runtime.md#nested-containers)). Since the containers run inside the *Workspace*'s own sandbox, they do not have any access to the host or to the *Cluster*, and they are stopped with the *Workspace*.

## Development Dependencies

The following *Template* installs Podman and starts PostgreSQL and Redis containers on every start of the *Workspace*. The containers use the *Workspace*'s network (i.e. `--net host`), which means that the application reaches them at `localhost`:

```yaml
spec:
  image:
    registry:
      url: golang:1.26-bookworm
  repository:
    url: https://github.com/acme-corp/payments-api
    authentication:
      http:
        username: x-access-token
        password:
          fromSecret: github-read-token.payments.cordium
  runtime:
    envVars:
      - key: DATABASE_URL
        value: postgres://postgres:postgres@localhost:5432/payments?sslmode=disable
      - key: REDIS_URL
        value: redis://localhost:6379
    tasks:
      - name: install-podman
        type: ON_CREATE
        runAsRoot: true
        onFailure: ON_FAILURE_ABORT
        run: |
          apt-get update
          apt-get install -y --no-install-recommends podman postgresql-client redis-tools
      - name: postgres
        type: POST_START
        onFailure: ON_FAILURE_ABORT
        run: |
          sudo podman rm -f postgres > /dev/null 2>&1 || true
          sudo podman run -d --name postgres --net host \
            -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=payments \
            -v payments-pgdata:/var/lib/postgresql \
            docker.io/library/postgres:18
          timeout 60 sh -c 'until pg_isready -q -h localhost; do sleep 1; done'
      - name: redis
        type: POST_START
        onFailure: ON_FAILURE_ABORT
        run: |
          sudo podman rm -f redis > /dev/null 2>&1 || true
          sudo podman run -d --name redis --net host docker.io/library/redis:8-alpine
          timeout 30 sh -c 'until redis-cli ping > /dev/null 2>&1; do sleep 1; done'
      - name: migrate
        type: POST_START
        workingDir: /workspace/repo
        run: go run ./cmd/migrate up
      - name: stop-containers
        type: PRE_STOP
        run: sudo podman stop --time 20 postgres redis
  limit:
    cpu:
      millicores: 4000
    memory:
      megabytes: 8192
    storage:
      megabytes: 40000
```

```bash
cordium create template payments-deps.payments.cordium --file payments-deps.yaml
```

Here are a few notes about this *Template*:

- The container images are only downloaded on the first start of a persistent *Workspace*, since the Podman storage is part of the *Workspace*'s storage.
- The PostgreSQL data lives in the `payments-pgdata` Podman volume, which also persists across restarts of a persistent *Workspace*. The `PRE_STOP` task stops the database gracefully before the *Workspace* stops.
- The containers count towards the *Workspace*'s CPU, memory and storage limits.

## Compose Files

If your repository already has a Compose file for its dependencies, you can run it via `podman-compose`. Here is the relevant part of a *Template* for a repository that has a `compose.dev.yaml` file at its root:

```yaml
spec:
  runtime:
    tasks:
      - name: install-podman
        type: ON_CREATE
        runAsRoot: true
        onFailure: ON_FAILURE_ABORT
        run: |
          apt-get update
          apt-get install -y --no-install-recommends podman podman-compose
      - name: services
        type: POST_START
        workingDir: /workspace/repo
        onFailure: ON_FAILURE_ABORT
        run: sudo podman-compose -f compose.dev.yaml up -d
      - name: stop-services
        type: PRE_STOP
        workingDir: /workspace/repo
        run: sudo podman-compose -f compose.dev.yaml stop
```

Ports published by the Compose services (e.g. `5432:5432`) are reachable at `localhost` inside the *Workspace*.

## Integration Tests in CI

The same approach works for ephemeral CI *Workspaces* that run integration tests against real dependencies instead of mocks. With `autoStop`, the *Workspace* stops as soon as the tests complete, and the run fails if they fail:

```yaml
spec:
  runtime:
    autoStop: true
    tasks:
      - name: integration-tests
        type: POST_START
        workingDir: /workspace/repo
        onFailure: ON_FAILURE_ABORT
        run: go test -tags integration -count 1 ./...
```

Added to a *Workspace* of the *Template* above, the `integration-tests` task runs after the `postgres`, `redis` and `migrate` tasks, since the *Workspace*'s tasks run after the *Template*'s (read more [here](https://octelium.com/docs/cordium/latest/workspaces/runtime.md#task-order)):

```bash
cordium create ws --template payments-deps.payments.cordium --ephemeral --start --file integration.yaml
```

To build and push container images from *Workspaces*, read [this example](https://octelium.com/docs/cordium/latest/examples/automation/image-build.md).
