Runtime templates
What Aidrop needs to know to run your repository — the runtime settings, the sizes, the plans, and the backing services.
A Service runs under a small, explicit contract: how it serves traffic, which
port, which health path, how much of a node it gets. Nothing detects it.
Every value is passed to service_build by the agent that wrote or read the
code, or defaulted — Aidrop does not read a repository to decide what it is,
and a build is the only thing that gets to say a revision does not run here.
The contract answers four questions:
| Field | What it decides |
|---|---|
runtime_kind |
How the application serves traffic: container (the default — the image is the authority), static_site, node_http, python_http or go_http. |
container_port |
The port the container listens on. Required the first time; nothing can default it. |
health_path |
The HTTP path Aidrop probes once the container runs. An application that answers nothing on its port, or 5xx here for longer than a start-up takes, is a failed deployment. Default /. |
resource_class |
The bounded CPU/memory/process envelope: micro, compact, dual, medium or large. A build that names none gets the smallest the plan includes. |
Beside them, required_secret_names — names only; the values themselves live
on the Project, and no read ever answers one.
The runtime kinds
A runtime kind is a family a Dockerfile scaffold exists for. The
runtime_envelope service_get answers carries a dockerfile for this
Service’s kind — the plainest of that family, binding the PORT the platform
sets, built and run under the platform’s constraints. Commit it, name it in
service_build with build_type: dockerfile and build_dockerfile_path, and
adjust the lines marked CHANGE ME. The default kind, container, has no
scaffold: it is the kind for an image that is its own authority.
| Runtime kind | Conventional port | Conventional health path |
|---|---|---|
container |
whatever the image listens on | / |
static_site |
8080 | / |
node_http |
3000 | /health |
python_http |
8000 | /health |
go_http |
8080 | /health |
These are conventions, not detections: service_build records the port you
pass, whichever kind you name. One scaffold per kind — there was a second set
keyed on the starter a Service was created from until starters were removed on
2026-09-01. What is left is precise about the platform’s constraints and
deliberately unopinionated about your layout.
Prefer a Dockerfile of your own to letting the build be inferred. With
build_type unset the image is built by nixpacks, which picks a runtime
version from what the repository declares — and picks an old default when it
declares nothing. An undeclared Node application is built with Node 18;
requires-python in pyproject.toml is ignored; Go 1.22 and 1.24 resolve to
an unspecified toolchain without raising an error. Declaring engines.node,
.nvmrc, .python-version or the go directive in go.mod is the cheap fix,
and a Dockerfile is the certain one.
Build type is separate
How an image is built is not how an application serves traffic, so the two are recorded independently — and how it is built is something you pass, never something Aidrop reads out of your code.
build_type: nixpacksis the default. The build pack is inferred and the Dockerfile is generated; your repository needs none.build_type: dockerfilebuilds your own, andbuild_dockerfile_pathsays which file that is —Dockerfile, orbuild/Dockerfile.apiin a monorepo. The path may be anywhere in the repository; the checkout root is handed to the build as its context whatever the file’s location, soCOPYpaths resolve from the root. A Dockerfile nobody named is never opened: committing one changes nothing until a build names it.
A build reads the Service’s recorded settings and nothing else.
A Dockerfile does not change the runtime kind. A Python service built from its
own Dockerfile is still python_http; container is the kind for an image
that is its own authority.
A repository nothing on this page describes is still deployable. Name
build_type: dockerfile, the port and the health path, and the image knows how
to build and run itself — that is all Aidrop was missing.
What the build records
service_build records the settings it built under, and service_get
reads them back as runtime_settings:
runtime_kind— how the application serves traffic;containerwhen the Dockerfile is the authority.container_portandhealth_path— the two the first build asks for.resource_class— the size, defaulting to the smallest the plan includes.build_type—nixpacks(the default) ordockerfile, andbuild_dockerfile_pathwith it when it isdockerfile. Passed, never detected.required_secret_names— the names of the Project values this Service reads, and the selection of which reach it: a Project value it does not name never does. The values themselves are stored on the Project, by a person in the dashboard or by an agent withproject_secret_set, and no read answers one back; a build is refused while a named value has none.
There is no preflight. A reading of the repository against this registry, with
an adaptations and missing_requirements checklist, was offered until
2026-09-09 and is gone: Aidrop runs no analyser over your code, and a build is
the only thing that gets to say a revision does not work here. When one stops,
it names what stopped it.
Sizes
A template says how an application serves traffic. A resource class says how much of a node it gets. Every container runs inside one, and the numbers are hard limits, not targets:
| Class | CPU | Memory |
|---|---|---|
micro |
0.5 vCPU | 512 MB |
compact |
1 vCPU | 1 GB |
dual |
2 vCPU | 2 GB |
medium |
2 vCPU | 4 GB |
large |
4 vCPU | 8 GB |
dual and medium share a CPU figure on purpose: one doubles the cores, the
other doubles the memory, and which you need depends on whether your
application is waiting or thinking. Every class caps processes at 256.
Storage is not on this table because there is none. The root filesystem is
read-only, with three small scratch mounts (/tmp, /run, /var/tmp) that
are emptied on every restart. Anything that must survive belongs in a database.
What your plan includes
Two limits, and they are separate. Slots are how many Projects you may have. The pot is how much memory and CPU everything inside them may claim between them.
| Plan | Slots | Pot | Classes | Backing services |
|---|---|---|---|---|
| Free | — | — | — | — |
| Trial | 1 | 768 MB · 0.75 vCPU | micro |
PostgreSQL |
| Starter | 1 | 512 MB · 0.5 vCPU | micro |
— |
| Pro | 2 | 3 GB · 3 vCPU | to dual |
PostgreSQL |
| Scale | 5 | 8 GB · 8 vCPU | to medium |
PostgreSQL, cache, queue |
| Business | 20 | 40 GB · 20 vCPU | to large |
PostgreSQL, cache, queue |
From Pro, a Team may also choose how big its database is: micro or compact
on Pro, plus dual on Scale, plus large on Business. The size is memory and
CPU out of the same pot, and it can be changed on a running instance — which
restarts it, so clients reconnect. Going back down is offered too; what a
smaller size never does is take disk away from a database that is using it.
Two things follow from the pot that the class list does not tell you. A class
your plan lists still has to fit — Pro lists dual at 2 GB and has 3 GB in
total, so one dual leaves room for a database and nothing else. And a stopped
application still holds its claim: the pot counts what is reserved, not what is
running, so a page that showed headroom the next deploy refused would be worse
than either number being slightly wrong.
Business runs on a cluster of its own rather than the shared one.
What each plan costs is on aidrop.it — this page is about what you get to run, not what it is priced at.
Databases, caches and queues
A backing service is a container aidrop.it runs beside your application and hands it a connection string for. You do not install or configure one; you ask for it, and its URL arrives as an environment variable at deploy.
| Service | Variable | Size | Durable |
|---|---|---|---|
| PostgreSQL | DATABASE_URL |
0.25 vCPU · 256 MB | Yes — on its own volume |
| Valkey cache | CACHE_URL |
0.25 vCPU · 128 MB | No — evicts under pressure |
| Valkey queue | QUEUE_URL |
0.25 vCPU · 256 MB | Yes — append-only, on its own volume |
These are deliberately smaller than any application class: a tuned PostgreSQL serving one small service needs far less than the application in front of it, and each one shares a node with the ingress. The size is memory and CPU, not disk — how much data you keep is not separately capped, and a cache or a queue is not application file storage either.
The queue is Valkey as well, and speaks redis://. The two Valkey services
differ in the way that matters for what you put in them: the cache evicts when it is full, and the queue refuses the
write, because work thrown away in silence is worse than a producer that finds
out. The queue keeps an append-only log on a volume of its own; the cache keeps
nothing.
The cache is Valkey rather than Redis. The redis:// scheme is unchanged, so
every client library you already use connects to it as before — the scheme is
how a client picks a protocol, and the protocol really is RESP.
Each variable names the role, not the product. CACHE_URL and QUEUE_URL
rather than REDIS_URL or the name of whatever runs behind the queue, the way
DATABASE_URL has always worked: your code says what it needs the connection for, and stays correct if
what is running behind it ever changes.
A backing service is the Project’s, one per kind per Project. Any Service in the Project can be attached to it, and each reads the same variable whether the service was created for that Service or already existed. Inside one PostgreSQL you can create separate databases and attach each to a different Service, so several Services share one pot claim while keeping their data apart.
Never hard-code a connection string. Read the variable — it is what makes the same code work whether the service was created for this Service or already existed.
Changing the settings
Pass the value to service_build again:
{ "service_id": "…", "resource_class": "medium", "required_secret_names": ["DATABASE_URL", "SMTP_PASSWORD"]}Every setting not passed is kept from the previous build. A runtime profile is immutable underneath — a changed value records a new one bound to the commit being built, rather than rewriting the one an earlier build ran under — and the resource envelope is copied in at that moment, so a later change to this table never alters a build that already ran.