# Mounts

A **Mount** is a per-namespace registry entry describing a shared
filesystem — an NFS export, an SMB share, an S3 bucket, a host
directory, a Kubernetes ConfigMap — that the runner attaches into a
job's workdir before dispatch. The server stores the kind-specific
config and validates the required fields at write time; everything
beyond that (mount-option syntax, credential enforcement, the actual
`mount -t <kind>` / rclone / aws-cli invocation) belongs to the runner.

> **Warning:** Mount **storage** — add / list / show / update / remove — is live.
> **Activation** — the runner actually mounting NFS / s3fs / bind /
> configmap into the sandbox before user code runs — is a daemon-side
> follow-up. Build the registry today; the wire-up at launch time lands in
> a later release.

For shipping *code* and moving *objects* in and out of jobs, see
[Storages](/get-started/storages.md) and
[Code mounts](/cli/examples/storage-code-mounts.md) — those paths work
end-to-end today. The Mounts registry is for network filesystems the
runner will attach wholesale.

## Kinds

Exactly ten kinds are accepted. The server validates required-field
presence per kind (422 on a miss) and stores the rest as-is.

| Kind | What it is | Required | Optional |
| --- | --- | --- | --- |
| `nfs` | NFS export. | `server`, `path` | `options` |
| `samba` | SMB/CIFS share (runner uses `mount -t cifs`). | `server`, `share` | `options`, `creds` |
| `ftp` | FTP server. | `server` | `path`, `port`, `creds` |
| `sftp` | SFTP over SSH (runner uses sshfs or curlftpfs). | `server` | `path`, `port`, `creds`, `key` |
| `s3` | S3-compatible bucket via a userspace client (aws-cli, boto, `rclone copy`). Objects pulled into the workdir on demand; no FUSE. | `bucket` | `endpoint`, `region`, `prefix`, `creds` |
| `s3fs` | The same bucket, FUSE-mounted with s3fs-fuse — POSIX-y file ops on a real path. | `bucket` | `endpoint`, `region`, `prefix`, `creds`, `options` |
| `google_drive` | A Drive folder (rclone-backed). `folderId` may be `root` for My Drive. | `folderId` | `creds` |
| `dropbox` | A path inside a Dropbox account (rclone-backed). | `path` | `creds` |
| `bind` | Host directory bind-mounted into the container. | `hostPath` | `readOnly` |
| `configmap` | A Kubernetes ConfigMap materialized into files. | `kubeNamespace`, `name` | `items` |

Note the spellings: `google_drive` takes an underscore, and `s3fs` has
no separator.

Three things worth knowing:

- **`s3` vs `s3fs`** — same required fields, different activation. Use
  `s3` for throughput and simple copy-on-read semantics; use `s3fs` when
  the job code expects a real filesystem path.
- **Non-AWS S3** — `endpoint` is the hook for Ceph, MinIO, R2, and
  Backblaze B2. Omit it and the runner assumes AWS S3.
- **Anonymous access** — credential refs are deliberately not required
  on kinds that can mount anonymously (public buckets, public NFS
  exports, anonymous FTP). The runner enforces credentials at activation
  time, where the error can be clearer than "field required".

> **Warning:** Some design notes describe a `git` mount that clones a repo at a
> resolved commit. It is not implemented: neither the control plane nor
> the CLI accepts `--kind git`. For shipping a working tree today, use
> `lakeshore code push` — see
> [Code mounts](/cli/examples/storage-code-mounts.md).

## CLI

```bash
lakeshore mounts add <name> --kind <kind> [--kwarg k=v ...] [--config-file <path>]
lakeshore mounts list [--json]
lakeshore mounts show <name> [--json]
lakeshore mounts update <name> [--kwarg k=v ...] [--config-file <path>]
lakeshore mounts remove <name>
```

The name must match `^[a-z0-9][a-z0-9-]*$`. Reusing a name inside a
namespace is a 409.

Config can come from repeatable `--kwarg key=value` entries (dotted keys
nest), from `--config-file` pointing at YAML or JSON, or from both on
`add` — the file seeds the config and the `--kwarg` entries override.
Values parse like the rest of the CLI: `true` / `false` / `null` and
numbers coerce, values starting with `[`, `{`, or `"` parse as JSON, and
everything else stays a string.

On `update` the two flags are mutually exclusive: `--kwarg` entries
deep-merge into the existing config (`--kwarg foo=null` drops a key),
while `--config-file` replaces the config wholesale.

## Secret references

Credentials belong in [Secrets](/api/auth-and-secrets.md#secrets), not in
mount config. A `{"$secret": "<name>"}` marker anywhere inside the config
resolves through the same plumbing that providers and tunnels use. The
dotted-key form on the CLI is:

```bash
lakeshore mounts add team-cache \
  --kind s3 \
  --kwarg bucket=my-team-cache \
  --kwarg 'creds.$secret=aws-team-keys'
```

Every referenced secret must already exist in the same namespace — the
server rejects the write with a 422 otherwise. A `$secret` marker is a
leaf: the walker does not recurse into sibling keys once it sees one,
and the value must be a non-empty string.

## Worked example

Register an NFS export of a dataset, mark it read-only for the runner,
and inspect it:

```bash
lakeshore mounts add imagenet \
  --kind nfs \
  --kwarg server=10.0.0.12 \
  --kwarg path=/exports/imagenet

# Runner-side mount options ride in `options` — deep-merged in.
lakeshore mounts update imagenet --kwarg options=ro

lakeshore mounts list
lakeshore mounts show imagenet --json
```

```json
{
  "name": "imagenet",
  "kind": "nfs",
  "config": {
    "server": "10.0.0.12",
    "path": "/exports/imagenet",
    "options": "ro"
  }
}
```

Larger configs read better from a file:

```yaml file="app-config.yaml"
kubeNamespace: training
name: app-config
items:
  - key: config.json
    path: config.json
```

```bash
lakeshore mounts add app-config --kind configmap --config-file ./app-config.yaml
lakeshore mounts remove app-config
```

## HTTP

```
POST   /v1/namespaces/:ns/mounts
GET    /v1/namespaces/:ns/mounts
GET    /v1/namespaces/:ns/mounts/:name
PATCH  /v1/namespaces/:ns/mounts/:name
DELETE /v1/namespaces/:ns/mounts/:name
```

`PATCH` replaces `config` wholesale — the CLI's deep-merge happens
client-side (fetch, merge, PATCH the result) — and the new config must
pass the same per-kind required-field gate as create.

These routes auto-create the namespace on first write, unlike the queue
and worker routes which 404 on an unknown namespace.

## Read next

- [Storages](/get-started/storages.md) — named S3 backends for moving data
  and code in and out of jobs (works end-to-end today).
- [Code mounts](/cli/examples/storage-code-mounts.md) — shipping git
  snapshots for reproducible remote execution.
- [Tunnels](/get-started/tunnels.md) — the parallel registry for WireGuard
  tunnels, with the same metadata-only status.
- [Secrets](/api/auth-and-secrets.md#secrets) — storing the credentials
  mounts reference via `$secret`.
