> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lazycloud.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Volumes and cloud buckets

> Files that outlive a container, shared by name across a workspace, and S3-compatible buckets you mount.

A volume is declared in Python and attached to each workload that needs it.
LazyCloud creates it on first mount and reuses it after that.

## Declare and use a volume

Save this as `reports.py`:

```python theme={null}
import json
from pathlib import Path

from lazycloud import App, Volume

app = App("reports")
reports = Volume("sales-reports", "/reports")


@app.function(volumes=[reports])
def save_report(amounts_cents: list[int]) -> dict[str, int]:
    summary = {"sales": len(amounts_cents), "total_cents": sum(amounts_cents)}
    Path("/reports/latest.json").write_text(json.dumps(summary))
    return summary


if __name__ == "__main__":
    print(save_report.remote([1200, 3500, 800]))
    reports.get("latest.json", "latest.json")
```

Run it:

```bash theme={null}
uv run python reports.py
```

The cloud function writes the report, and the script downloads it to your
current directory. Within a workspace a volume name always means the same
files, even from another app. A new name is a new, empty volume, and removing a
mount or a deployment never deletes stored files. Volumes live
[with their workspace](/platform/compute#where-a-workload-runs) and bill per
GiB-month.

## Upload inputs before the first run

A local setup script can seed a volume before any workload runs:

```python theme={null}
from lazycloud import Volume

models = Volume("models", "/models")
models.create()
models.put("weights.pt", "weights.pt")
```

A function with `volumes=[models]` then reads `/models/weights.pt`. `create()`
reuses an existing volume. Setup like this belongs behind
`if __name__ == "__main__":`, so importing the module never uploads anything.

## Read and write from Python

Inside a container a volume is a directory. Locally, the `Volume` object reads
and writes it:

```python theme={null}
from reports import reports

print(reports.read_text("latest.json"))
print(reports.list())
print(reports.stat("latest.json"))
reports.get("latest.json", "downloaded-report.json")
```

`put()`, `write_text()`, `write_bytes()`, `read_bytes()`, `list_path()`, and
`move()` cover the rest. Multi-gigabyte uploads go in parts through
`create_multipart_upload()` and its companions.

## Share files between containers

Every container that mounts a volume sees the same files. Under the directory
is object storage, which sets a few rules:

* A file appears to other containers, and becomes durable, when its writer
  closes it.
* Two writers to one path don't merge. The last close wins, so each call writes
  its own path, such as `/reports/{task_id}.json`.
* There are no locks. A [queue or map](/concepts/queues-maps) coordinates turns.
* Appending to a large file rewrites it, so logs fit better as many small files.
* Large reads are cached on the machine, which makes volumes good for model
  weights.

Weights downloaded once serve every container:

```python theme={null}
from pathlib import Path

from lazycloud import App, GpuType, Volume

app = App("inference")
models = Volume("models", "/models")
WEIGHTS = Path("/models/encoder.safetensors")


def ensure_weights() -> None:
    if WEIGHTS.exists():
        return
    partial = WEIGHTS.with_suffix(".part")
    download_weights(partial)   # your download code
    partial.rename(WEIGHTS)


@app.function(gpu=GpuType.L4, volumes=[models], on_start=ensure_weights, concurrency=4, in_process=True)
def encode(text: str) -> list[float]:
    ...
```

Writing to `.part` and renaming means no container ever sees a half-written
file. Two containers starting together may both download, which wastes one
download but corrupts nothing.

## Mount an existing cloud bucket

`CloudBucket` mounts an S3-compatible bucket you own, reading and writing it in
place.

```python theme={null}
from pathlib import Path

from lazycloud import App, CloudBucket, CloudBucketConfig

app = App("etl")
bucket = CloudBucket(
    "my-data-bucket",
    "/data",
    CloudBucketConfig(
        access_key="MY_BUCKET_ACCESS_KEY",
        secret_key="MY_BUCKET_SECRET_KEY",
        region="us-east-1",
    ),
)


@app.function(volumes=[bucket])
def read_text_file(key: str) -> str:
    return (Path("/data") / key).read_text()
```

`access_key` and `secret_key` name [secrets](/concepts/secrets). Without them,
the mount uses the machine's own credentials, such as an instance role in your
[AWS account](/platform/compute). `bucket`, `prefix`, `endpoint`,
`force_path_style`, and `read_only` cover other layouts and stores. The bucket
must already exist. See the [Parquet example](/examples/parallel-parquet-s3).

## Delete stored data

`reports.remove("latest.json")` deletes one file and `reports.delete()` the whole
volume. A workload that still mounts it recreates it empty on its next run. The
[file commands](/cli/storage#files) work from a terminal too.
