Skip to content

Deploying on Hop3

Hop3 builds the application from a directory you upload, runs each Procfile entry, provisions PostgreSQL, and puts a proxy in front. One command installs the application.

Export the files

$ myxo export layouts/app.toml hop3 --name guestbook --path layout.toml --out dist
dist/Procfile
dist/hop3.toml
dist/env.example

hop3.toml declares a PostgreSQL addon, whose DATABASE_URL your layout reads, runs the migrations before each run, and compiles bytecode at build:

[metadata]
id = "guestbook"

[run]
before-run = "myxo migrate layout.toml"

[env]
# Bytecode compiled when the application is built: its processes then start
# without compiling each module (E10).
UV_COMPILE_BYTECODE = "1"

[[addons]]
type = "postgres"

[healthcheck]
path = "/healthz"

hop3check dist validates the file without reaching a server.

The health path

Hop3 probes this path on the public port during a deploy, so your web service must answer it. The guestbook's web service serves only /entries: either add a /healthz route, or give an existing path with --health /entries.

Build the directory

The directory holds your application as a wheel; Hop3 installs Myxo from PyPI. Run these in the tutorial's guestbook/:

$ uv build --wheel --out-dir dist/wheels
$ rm dist/wheels/.gitignore
$ cp layouts/app.toml dist/layout.toml

Remove that .gitignore: uv marks its output directory as ignored, which the upload would honour. Then write dist/pyproject.toml, a project that depends on Myxo and on your wheel:

[project]
name = "guestbook-deploy"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = ["myxo[postgresql]", "myxo-guestbook"]

[tool.uv]
package = false

[tool.uv.sources]
myxo-guestbook = { path = "wheels/myxo_guestbook-0.1.0-py3-none-any.whl" }

Add echo 3.14 > dist/.python-version, and lock it with (cd dist && uv lock). uv replaces the server's older interpreter with a managed 3.14, with nothing to configure. To keep the versions you tested, copy them from your project's lock into constraint-dependencies under [tool.uv], as E10 did.

Once the lock names an ASGI framework such as Litestar, hop3check dist reports asgi-no-start, since it looks for a command in [run].start and this one is in the Procfile. myxo serve runs its own ASGI server, so the failure that rule warns of doesn't apply. The rule belongs to hop3check alone. The server has no such check, so it won't stop hop3 deploy. E10's testbed used no such framework, so this pairing has not itself been deployed.

Deploy

Give these commands to whoever operates the server:

$ hop3 deploy --yes --app guestbook dist

Set any variable that your layout needs and the addon doesn't inject, with hop3 env set --app guestbook NAME=value. Hop3 restarts a process that dies, at once.

Layout notes

  • Addresses. On the Hop3 0.7.5 of E10, the proxy reached 127.0.0.1:$PORT, so loopback defaults fit as they are.
  • Internal calls. Use Unix sockets, unix:run/web.sock, which need no port. Fixed ports for listeners would collide with another application's on the same server.
  • Reduced privileges. Hop3 cannot sandbox a process, so the export refuses a process with privileges = "reduced". --without-sandbox accepts full privileges, and records that in a comment in hop3.toml.
  • Replicas. The export refuses more than one instance of a process.

Limits

E10 ran this on Hop3 0.7.5 with a warm uv cache, reached through an ssh tunnel. It has not been tried with a domain and TLS, or with a cold cache.