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

# Local and remote builds

> Cycls builds a container three ways: on your Docker, on a shared cloud executor, and on the deploy service. Know which one runs and what it contains.

This page is for developers who want to know where a build happens, what goes
into it, and how long it takes. It applies to functions, apps and agents, since
all three share one build pipeline.

## The three paths

| Command                      | Where the image is built | Docker needed locally | Which code runs           |
| ---------------------------- | ------------------------ | --------------------- | ------------------------- |
| `cycls run file.py`          | your machine             | yes                   | the code you are holding  |
| `cycls run file.py --remote` | Cycls Cloud              | no                    | the code you are holding  |
| `cycls deploy file.py`       | Cycls Cloud              | no                    | the code frozen at deploy |

`cycls shell file.py` and `f.build()` also build on your machine.

## Local build

```bash theme={null}
cycls run examples/hello.py
```

Cycls generates a Dockerfile, assembles a context directory, and calls your local
Docker daemon. The container then runs with the port published, and the file
watcher rebuilds on save.

The generated Dockerfile is deterministic:

```docker theme={null}
FROM python:3.12-slim-bookworm
ENV PIP_ROOT_USER_ACTION=ignore PYTHONUNBUFFERED=1
WORKDIR /app
RUN pip install uv
RUN apt-get update && apt-get install -y --no-install-recommends <your apt packages>
RUN uv pip install --system --no-cache <your pip packages>
RUN <your run commands>
COPY context_files/<dst> /app/<dst>
```

The base image is `python:<your version>-slim-bookworm`. The Python version is
pinned to your host's major and minor version, because cloudpickle bytecode does
not cross Python versions.

**When to use it:** you have Docker, you want no cloud round trip, and your
dependencies are small enough that a rebuild is quick.

**When not to use it:** the image is large, your machine is slow, or you are on a
laptop without Docker. Use `--remote` instead.

## Remote build for functions

```bash theme={null}
cycls run examples/simulate.py --remote --n 1000
```

The function's bytecode is sent to an executor, which is a small service
provisioned once per image configuration and named `exec-<hash>`. Every function
that declares the same image shares it.

```
first call    about 90 seconds while the executor is provisioned
later calls   network plus compute, roughly 1 second per save
```

Print statements from the remote process stream back to your terminal while it
runs. Edit the function, save, and the new bytecode runs on the same warm
executor with no redeploy.

Executors appear in `cycls ls` as `exec-*`. They scale to zero and cost nothing
while idle. `cycls rm` removes one.

## Remote build for apps and agents

```bash theme={null}
cycls run examples/notes.py --remote
#   https://dev-notes.cycls.ai
#   │ 200 GET /
```

A dev service named `dev-<name>` is deployed once. Each save hot swaps the
running application inside it, and the request log streams into your terminal.
This gives you a public HTTPS URL during development, which is what OAuth
callbacks and mobile clients need.

## Deploy build

```bash theme={null}
cycls deploy notes.py
# Checking 'notes'...
# Deploying 'notes'...
#   [BUILDING]  ...
#   [DEPLOYING] ...
#   [DONE]      https://notes.cycls.ai
```

Deploy does three things:

1. Checks that the name is available for your account.
2. Builds a source archive locally: the generated Dockerfile, your copied files
   under `context_files/`, `function.pkl` holding the cloudpickled function, and
   an entrypoint.
3. Uploads the archive as `tar.gz` to the deploy API and streams build events
   back as NDJSON: `BUILDING`, then `DEPLOYING`, then `DONE` or `ERROR`.

The container image is built in the cloud, so deploying does not need Docker
locally.

<Warning>
  The source archive is capped at 100 MiB. Ship large files through a
  [volume](/build/volumes) with `cycls volume put`, not through `.copy()`.
</Warning>

## Image caching

Cycls hashes the image declaration into the tag. The hash covers:

* the base image and Python version
* pip packages, apt packages and run commands
* the path and content hash of every copied file

Identical inputs reuse the cached image. Changing one package rebuilds only from
that layer onward. Force a clean build with `.rebuild()`:

```python theme={null}
image = cycls.Image().pip("numpy").rebuild()
```

## Verify what was built

```bash theme={null}
cycls shell notes.py
# Entering cycls/notes:730f149a (exit to leave)
root@a1b2c3:/app# uv pip list
root@a1b2c3:/app# ls /app
```

`cycls shell` builds or reuses the same image and drops you into `/app`. Use it
to confirm a package landed or a build command worked before adding more.

## Timing

| Event                                     | Typical duration     |
| ----------------------------------------- | -------------------- |
| First local build of a new image          | 1 to 2 minutes       |
| Cached local rebuild                      | a few seconds        |
| First executor provision per image config | about 90 seconds     |
| Warm remote call                          | network plus compute |
| Cold start of an idle deployment          | a few seconds        |

## Troubleshooting

<AccordionGroup>
  <Accordion title="Docker is not running">
    Local builds need the daemon. Start Docker Desktop on macOS or Windows, or
    `sudo systemctl start docker` on Linux. To avoid Docker entirely, use
    `cycls run --remote` and `cycls deploy`.
  </Accordion>

  <Accordion title="FileNotFoundError: Path in 'copy' not found">
    `.copy()` paths are resolved when the image hash is computed, relative to your
    working directory. Check the path, or pass an absolute one.
  </Accordion>

  <Accordion title="ModuleNotFoundError inside the container">
    A module imported from another file of yours travels by reference, not in the
    pickle. Bundle it with `cycls.Image().copy("helpers.py")`. Copied files land in
    `/app`, which is on `sys.path`.
  </Accordion>

  <Accordion title="The build succeeds but the container exits at boot">
    Builder code is pickled by value. Module-level names referenced inside class
    bodies or dataclasses defined in the deployed function can fail to resolve in the
    container. Reproduce with `cycls run` before deploying.
  </Accordion>

  <Accordion title="Port already in use">
    Pass another port: `cycls run file.py` serves on 8080 by default, and
    `my_app.local(port=3000)` changes it.
  </Accordion>
</AccordionGroup>

## Next

<Card title="Deploy" icon="rocket" href="/ship/deploy">
  Names, sizing, environment variables and what survives a redeploy.
</Card>
