# Run Tiled Server in a Container

There is an official Tiled container image for use with
[Docker](https://www.docker.com/) or [podman](https://podman.io/).

Download the Tiled container image.

```
docker pull ghcr.io/bluesky/tiled:latest
```

It is best practice to use a specific tag instead of `latest`.
See the [list of tiled image versions on GitHub](https://github.com/bluesky/tiled/pkgs/container/tiled)
for tags.

```{note}

Some of the examples below set an environment variable
`TILED_SINGLE_USER_API_KEY` to `secret`, as a placeholder. For actual use, use
a difficult-to-guess secret. Two equally good ways to generate a secure
secret...

With ``openssl``:

    openssl rand -hex 32

With ``python``:

    python -c "import secrets; print(secrets.token_hex(32))"

```

## Example: A writable catalog

```
docker run \
  -p 8000:8000 \
  -e TILED_SINGLE_USER_API_KEY=secret \
  ghcr.io/bluesky/tiled:latest
```

**The data and database are inside the container and will not persist outside
it.** Read on to persist it.

## Example: A persistent writable catalog

We will create and mount a local directory, `./storage` which will be used to
hold uploaded data and # a (SQLite) database to index the metadata.

```
mkdir storage/

docker run \
  -p 8000:8000 \
  -e TILED_SINGLE_USER_API_KEY=secret \
  -v ./storage:/storage ghcr.io/bluesky/tiled:latest
```

## Example: Serve a directory of existing files

We will point Tiled at a (read-only) directory of files and ask it to crawl and
serve them. If you don't have scientific data files at hand to try this with, you can
quickly generate some with:

```
# Optional: Generate sample files... TIFF, Excel, HDF5, etc.
python -m tiled.examples.generate_files data/
```

### Quick Start (Not Scalable)

This approach is nice for development and rapid iteration. It indexes the files
at server startup.

```
docker run \
  -p 8000:8000 \
  -e TILED_SINGLE_USER_API_KEY=secret \
  -v ./data:/data:ro \
  ghcr.io/bluesky/tiled:latest \
  tiled serve directory --host 0.0.0.0 /data
```

Two problems with this one-line approach:

* If you restart the server, all the indexing work is re-done from scratch.
* If you horizontally scale with multiple containers, each one will crawl the
  filesystem individually, putting load on the filesystem and potentially getting
  views of the filesystem that are out of sync.

Read on for a scalable approach.

### Scalable to Multiple Processes

Create a place outside the container to store the "catalog", `catalog.db`.

```
mkdir storage/
```

Start the server, potentially multiple on different ports.

```
docker run \
  -p 8000:8000 \
  -e TILED_SINGLE_USER_API_KEY=secret \
  -v ./data:/data:ro \
  -v ./storage:/storage \
  ghcr.io/bluesky/tiled:latest
```

Register the files in the directory `data/` with the catalog.

```
docker run \
  -e TILED_SINGLE_USER_API_KEY=secret \
  -v ./data:/data:ro \
  -v ./storage:/storage \
  ghcr.io/bluesky/tiled:latest \
  tiled catalog register /storage/catalog.db /data --verbose
```

### Scalable to Multiple Hosts

We will use one container to run a database server,
and additional containers to run tiled services.
To connect these services we create a bridge network.

```
docker network create -d bridge tilednet
```

```{note}
We chose to use a custom bridge network here for two reasons:
1. The host network is not supported for Docker Desktop for Mac/Windows
2. The default bridge network does not resolve container DNS entries by hostname
```

Instead of the default SQLite database, we need to use a PostgreSQL database.
One way to run a PostgresSQL database is:

```
export TILED_DATABASE_PASSWORD=db_secret
mkdir postgres-data
docker run -d \
  --name tiled-test-postgres \
  --net=tilednet \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=${TILED_DATABASE_PASSWORD} \
  -v ./postgres-data:/var/lib/postgresql/data \
  docker.io/postgres:16
```

Initialize the database. (This creates the tables, indexes, and so on used by Tiled.)

```
export TILED_DATABASE_URI=postgresql+asyncpg://postgres:${TILED_DATABASE_PASSWORD}@tiled-test-postgres:5432

docker run --net=tilednet ghcr.io/bluesky/tiled:latest tiled catalog init $TILED_DATABASE_URI
```

Create a directory for Tiled configuration, e.g. `config/`.

```
mkdir config/
```

Place a copy of `example_configs/single_catalog_single_user.yml`, from the Tiled
repository root, in this `config/` directory.

Replace the line:


```yaml
uri: "sqlite+aiosqlite:////storage/catalog.db"
```

with a PostgreSQL database URI, such as:

```yaml
uri: "postgresql+asyncpg://postgres:${TILED_DATABASE_PASSWORD}@tiled-test-postgres:5432"
```

Start the server, potentially multiple servers across many hosts.

```
docker run \
  --net=tilednet \
  -p 8000:8000 \
  -e TILED_SINGLE_USER_API_KEY=secret \
  -e TILED_DATABASE_PASSWORD=${TILED_DATABASE_PASSWORD} \
  -v ./config:/deploy/config:ro \
  -v ./data:/data:ro \
  ghcr.io/bluesky/tiled:latest
```

Register the files in the directory `data/` with this catalog.

```
docker run \
  --net=tilednet \
  -e TILED_SINGLE_USER_API_KEY=secret \
  -e TILED_DATABASE_PASSWORD=${TILED_DATABASE_PASSWORD} \
  -v ./config:/deploy/config:ro \
  -v ./data:/data:ro \
  ghcr.io/bluesky/tiled:latest \
  tiled catalog register ${TILED_DATABASE_URI} /data--verbose
```

## Example: Custom configuration

There are configuration examples located in the directory `example_configs`
under the Tiled repository root. The container image has one in particular,
`single_user_single_catalog.yml`, copied into the container under
`/deploy/config/`. Override it by mounting a local directory an
`/deploy/config` as shown:

```
docker run \
  -p 8000:8000 \
  -e TILED_SINGLE_USER_API_KEY=secret \
  -v ./config:/deploy/config:ro \
  ghcr.io/bluesky/tiled:latest
```

You may need to mount additional volumes as well.

## Example: Run Tiled with a dashboard of metrics

See {doc}`../how-to/metrics`.

## Next Steps

See {doc}`../explanations/security` and {doc}`../explanations/access-control`
for examples addressing authentication and authorization.

See {doc}`../reference/service-configuration` for a comprehensive reference.
