Services

Directories that Encore runs, tests and deploys as a unit

An Encore service is a directory with an encore.service.json file in it. Every Python file in that directory and its subdirectories belongs to the service. Encore runs each service as its own process, gives it its own tests, and grants it access to the infrastructure its code uses.

Services are declared in this file rather than in Python, so Encore.py has no service class or decorator. The service's code is an ordinary Python project with your own web framework.

Defining a service

A minimal service file names the service, sets its language, and tells Encore which HTTP paths the service serves:

encore.service.json
{ "name": "orders", "lang": "python", "api": [ { "type": "http", "prefix": "/", "name": "orders", "expose": true } ] }

Next to it go the service's code, its dependencies and its lockfile:

my-app/ ├── encore.app └── orders/ ├── encore.service.json ├── pyproject.toml ├── uv.lock ├── main.py ├── resources.py └── migrations/ └── 1_create_orders.up.sql

A single-service app can also put encore.service.json at the app root, next to encore.app.

The file is parsed as strict JSON: no comments and no trailing commas.

The service file

FieldRequiredDescription
nameYesThe service's name. Must be non-empty and unique within the app.
langYes"python" for an Encore.py service. Selects the defaults for run, test and build.
descriptionNoA sentence or two about what the service is for, shown in Encore's tools.
apiNoThe HTTP paths (or API specs) the service serves, and whether the API gateway exposes them. See Serving HTTP.
runNoHow encore run starts the service. See Running and testing a service.
testNoHow encore test runs the service's tests.
buildNoHow the service is built for deployment.
dockerNoHow encore build docker packages the service: base_image and entrypoint.
workersNoThe service's background processes, which serve no requests. See Workers.

Serving HTTP

Encore.py has no API framework, so a Python service usually declares a single http entry: a path prefix the service serves itself, with whatever framework you choose. Encore doesn't need to know the routes below the prefix; it forwards requests at or below it to the service.

"api": [ { "type": "http", "prefix": "/", "name": "orders", "expose": true } ]
FieldDescription
prefixThe path prefix, starting with /.
nameThe entry's name, a valid identifier ([A-Za-z_][A-Za-z0-9_]*). Defaults to the prefix's last segment, so a prefix of / needs an explicit name.
exposeWhether the app's API gateway forwards outside requests to the prefix: true, false (the default), or a list of gateway names.
methodsThe uppercase HTTP methods to forward. Omit it to forward all of them.

A service can list several entries, for example a public prefix and an internal one:

"api": [ { "type": "http", "prefix": "/orders", "expose": true }, { "type": "http", "prefix": "/internal", "name": "internal_orders" } ]

Instead of an http prefix, a service can describe what it serves with an OpenAPI document ({ "type": "openapi", "spec": "openapi.yaml" }), a GraphQL schema ({ "type": "graphql", "schema": "schema.graphql" }) or protobuf definitions ({ "type": "grpc", "protos": "proto" }). Paths are relative to the service directory and can't leave it.

Running and testing a service

run, test and build are each an object with a type. For a Python service they default to type "python", which has no options:

  • run starts the service's entry point, main.py (or app.py), through the package manager detected from the lockfile in the service directory: uv run main.py with a uv.lock, poetry run python main.py with a poetry.lock, and so on. Without a lockfile it runs python3 main.py. See Installing the SDK for the full list.
  • test runs pytest the same way, for example uv run pytest.
  • build packages the service's source tree together with its dependency manifests and lockfile, and recreates the environment with the same package manager on deployment.

Encore starts the command in the service's directory and passes the address to listen on in the ENCORE_LISTEN_ADDR environment variable, as host:port.

To use another command, set the type to custom and give the command as a list of arguments:

{ "name": "orders", "lang": "python", "run": { "type": "custom", "cmd": ["uv", "run", "python", "-m", "orders"] }, "test": { "type": "custom", "cmd": ["uv", "run", "pytest", "-x"] } }

A custom run can also have a build command, run to completion each time before cmd starts. The command is run as given, not through a shell.

Multiple services

An app can have any number of services. Each one is a directory with its own encore.service.json, and, for Python, its own dependencies and lockfile, since Encore detects the package manager per service directory:

my-app/ ├── encore.app ├── orders/ │ ├── encore.service.json │ ├── pyproject.toml │ ├── uv.lock │ ├── main.py │ └── db.py └── billing/ ├── encore.service.json ├── pyproject.toml ├── uv.lock ├── main.py └── invoices.py

Services in one app don't have to share a language: a Python service can sit next to Go, TypeScript or Rust services, and use resources they declare. Start with one service and split it when there's a reason to; App Structure discusses where to draw the boundaries.

Which service owns what

Encore decides which service a file belongs to from its location: a file belongs to the service whose directory contains it. This is what Encore uses to grant access to infrastructure.

  • A resource is declared by the service whose directory holds the declaration. For example, a postgres.Database("orders", migrations="./migrations") in orders/db.py is the orders service's database, with migrations in orders/migrations.
  • Every use is attributed to the service whose directory contains the code using it, even if another service imports that file. If billing imports a helper from orders/ that queries a database, the query is still recorded for orders.
  • Uses must be inside a service. Code outside every service directory, such as a shared library, can declare resources but can't use them directly. Have the service pass it a handle instead, for example db.read() or db.write() for a database.

To use another service's resource, reference it by name in your own service rather than importing the other service's code. For a database that's postgres.Database.named("orders"); buckets and topics have the same named method.

Service URLs

Your services call each other over HTTP with any client you like. To find another service's address, use base_url from encore.services:

billing/invoices.py
import httpx from encore.services import base_url async def fetch_order(order_id: int) -> dict: async with httpx.AsyncClient() as client: response = await client.get(f"{base_url('orders')}/orders/{order_id}") response.raise_for_status() return response.json()

base_url returns the service's configured base URL with no trailing slash, so the request path goes right after it. It reads the runtime configuration and makes no network request. A service name the process doesn't know raises ValueError.

Looking up a service's URL makes the calling service depend on it, and Encore reads that dependency from your code. So:

  • Pass the service name as a string literal: base_url("orders"), not base_url(name).
  • Call base_url directly. Don't store it in a variable or pass the function around.
  • Call it from code inside a service.
  • Name a service that exists in the app. Encore reports an unknown name when it parses the app.

A service looking up its own URL is allowed, and doesn't count as a dependency.