Skip to content

ajolopy deploy

Spec

Full specification: specs/cli-deploy.md · Item: AJ-37 · Per-target follow-ups: AJ-42 / AJ-43 / AJ-44 / AJ-45.

Purpose

ajolopy deploy <target> emits the per-target deploy manifests your project needs to ship. v0.1 covers five targets behind a single command:

Target Manifest(s) emitted
docker Dockerfile.prod + .dockerignore. Works on k8s / VPS / ECS / on-prem / any container runtime. Reference implementation.
fly fly.toml. Generates the manifest and prints the fly launch / fly deploy flow (AJ-42).
railway railway.json. Generates the manifest and prints the railway login / railway link / railway up flow for you to run.
render render.yaml. Generates the Blueprint Render's dashboard applies; users commit/push the file and apply it from https://dashboard.render.com/blueprints.
vercel vercel.json after an interactive warning gate about Vercel's limitations for Python AI apps. Pass -y / --yes to skip the prompt.

The command does not invoke docker build, flyctl deploy, or any platform CLI in v0.1 — it generates the manifests and prints the next steps you run yourself. That keeps tests hermetic and makes the command safe to run anywhere.

Reach for ajolopy deploy when you are ready to ship: every project generated by ajolopy new is one ajolopy deploy <where> away from a deployable artifact.

Signature

ajolopy deploy <target> [--out PATH] [--dry-run] [--force] [-y | --yes]

Arguments and flags

Argument / flag Type / values Default Description
target docker | fly | railway | render | vercel required Which deploy target to render. --help lists every registered target with its short description.
--out PATH path cwd Project root to write manifests into. Must exist and be a directory.
--dry-run flag off Print manifests to stdout with a # <path> header per file. Writes nothing.
--force flag off Overwrite existing manifest files. Without it, the command refuses with a hint.
-y, --yes flag off Skip interactive confirmation prompts (forward-compatible with AJ-45's Vercel warning gate).

Exit codes

Code Constant Meaning
0 EXIT_OK Manifests written (or --dry-run succeeded).
1 EXIT_USER_ABORT User declined an interactive prompt (Vercel warning gate).
2 EXIT_USAGE Unknown target, missing positional, files exist without --force, or --out points at a non-directory.
3 EXIT_INTERNAL Target rendering failed (template error, IO error, malformed pyproject.toml).

Quick example

The shortest path is the docker reference target — it works on any machine without a platform account:

$ ajolopy deploy docker
Wrote:
  /workspace/acme-support/Dockerfile.prod
  /workspace/acme-support/.dockerignore

Next steps:
  docker build -f Dockerfile.prod -t acme-support:latest .
  docker run -p 3000:3000 --env-file .env acme-support:latest

To preview without writing:

$ ajolopy deploy docker --dry-run
# Dockerfile.prod
# syntax=docker/dockerfile:1.7
...

# .dockerignore
__pycache__/
*.py[cod]
...

To overwrite an existing file:

$ ajolopy deploy docker --force

The cloud targets all generate their platform-specific manifest and print the next-step commands for you to run. For example:

$ ajolopy deploy railway
Wrote:
  /workspace/acme-support/railway.json

Next steps:
  railway login
  railway link
  railway up

The vercel target ships an interactive warning gate before it writes the manifest:

$ ajolopy deploy vercel
Vercel for Python AI apps has serious limitations:
  - Serverless function timeout: 300s max (Pro plan)
  - No persistent in-process state (in-memory Memory backends fail)
  - Cold starts can break long SSE streams

If your app has:
  - Workflows > 5 min          -> use Fly.io or Railway
  - Persistent in-proc memory  -> use Fly.io or Railway
  - Long streaming             -> use Fly.io or Railway

Vercel works well for:
  - Single-turn short agents
  - Batch endpoints
  - Non-streaming APIs

Continue with Vercel? (y/N) y
Wrote:
  /workspace/acme-support/vercel.json

Next steps:
  vercel login
  vercel link             # link to an existing project
  vercel deploy --prod    # production deploy

Pass -y / --yes to skip the prompt in scripted runs. Declining the gate exits EXIT_USER_ABORT (1) without writing anything.

Escape hatches

  • Custom targets. Implement the DeployTarget protocol and call register_target(MyTarget()) at import time. The registry uses last-write-wins semantics — that is how AJ-42 / AJ-43 / AJ-44 / AJ-45 will replace the in-tree stubs once their implementations land.
  • Programmatic invocation. The _command helper takes an argparse.Namespace and explicit stdout / stderr / cwd streams; tests use it without going through the console-script wrapper.
  • Pyproject-driven project name. [project] name in pyproject.toml becomes the Docker image tag. Without a pyproject.toml, the command falls back to the project directory name.

Common gotchas

  • The command does not invoke docker build, flyctl, or any other platform CLI. v0.1 stops at generating manifests and printing next steps. Run the printed commands yourself.
  • A second invocation against the same project fails fast unless you pass --force (the command refuses to overwrite without explicit consent). The original files are untouched.
  • --out must point at an existing directory. Pass --out /tmp/scratch to render against a clean slate during demos.
  • The docker target reads the current Python interpreter's sys.version_info for the Dockerfile base image. Pinning your project to a different version is a follow-up (no --python-version flag in v0.1 — call render_dockerfile(python_version=...) directly from your own script if you need it).
  • The vercel target is the only v0.1 target that prompts the user inside prepare(). The injectable stdin / stdout on VercelTarget keep tests hermetic; pass -y to skip the gate in CI or scripted flows.

See also