Skip to main content
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

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

Local build

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:
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

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.
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

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

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.
The source archive is capped at 100 MiB. Ship large files through a volume with cycls volume put, not through .copy().

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():

Verify what was built

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

Troubleshooting

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.
.copy() paths are resolved when the image hash is computed, relative to your working directory. Check the path, or pass an absolute one.
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.
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.
Pass another port: cycls run file.py serves on 8080 by default, and my_app.local(port=3000) changes it.

Next

Deploy

Names, sizing, environment variables and what survives a redeploy.