Skip to main content

Extra machines

Run containers of your own — a background remover, a thumbnailer, some model nobody else needs — on a machine beside your site

The catalogue covers what most applications want beside them: a database, a cache, a search engine, a mail relay. It will never cover the rest. A real site wants a background remover, an OCR engine, a barcode reader, some model nobody else on the platform has heard of.

An extra is a machine of your own for exactly that. You buy it in the configurator with the environment, or add it later from the project's Resources tab, and you say what runs on it with an ordinary compose file in your repository. A shared plan has none.

It is on your environment's private network, it has its own disk, and it runs your containers and nothing else.

Each environment has its own. Staging and development read the same vallic.yaml, and production's extra is not theirs: its ports are on production's private network. Give staging one of its own on the Resources tab — a machine at full price, not a share of the machine staging runs on. Until it has one, an extra staging or development has no machine for is left out rather than refused: the site deploys, the tool is not there, and no EXTRA_* variable names it. Production is different — a deploy that declares an extra production has no machine for is refused, because the site would be dialling a tool that does not exist. Add the machine on the Resources tab and deploy again.

Two files

vallic.yaml says an extra exists and what your application may reach on it:

extra:
  voyager:
    expose:
      rembg: 8001:8000
      easyocr: 8002:8000

.vallic/extra/voyager.yml says what runs — an ordinary compose file:

services:
  rembg:
    image: ghcr.io/acme/rembg-api:1.4.0
    environment:
      U2NET_HOME: /cache/rembg
    volumes:
      - cache/models/rembg:/cache/rembg
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"]

  easyocr:
    image: ghcr.io/acme/easyocr-api:3.2.0
    volumes:
      - cache/models/easyocr:/cache/easyocr

They are two files because they answer to different rules. vallic.yaml is this platform's, and every key in it is checked. Your compose file is yours.

Slots

Extras are called pioneer, voyager, galileo, magellan, cassini and juno, allocated in that order — buy two and you have pioneer and voyager.

The name is the machine, its hostname on the private network, the file it runs and the line on your invoice, which is why it is ours to hand out rather than yours to invent.

Ports

8001:8000 reads as compose's own port syntax: 8001 is the port the rest of your environment reaches, 8000 is the port your container listens on. A single number means both.

A service can answer on several:

expose:
  api:
    - 8003:8000
    - 8004:9090

The ports your environment reaches come from a reserved range, 8000 to 8008. What your container listens on inside itself can be anything — 8000:80 is fine. No two services on one extra may use the same reachable port; they share a machine, so they share an address.

You do not write ports: in your compose file. The platform writes them, bound to the private network. A compose file that could choose an address could put your API on the public internet.

Your data

An extra has its own disk. A volume names a directory on it, and the first word says whether it is backed up:

volumes:
  - keep/results:/var/results      # backed up
  - cache/models/rembg:/cache/rembg  # not backed up
  - keep/reference:/data/reference:ro   # read-only, and backed up
  • keep/ is for anything you would be sorry to lose. It is included in your backups. Restoring it is done by support for now: ask, and say which backup.
  • cache/ is for anything the container can fetch or rebuild — model weights, scratch files, thumbnails it can make again. It is never backed up.
  • code/ is your own files, out of your repository. See below.

There is no default, and you have to pick one. A file that names neither is refused rather than guessed at. The two choices are wrong in opposite directions: backing up a model cache costs you storage and makes every backup slower, and not backing up real data costs you the data — and we cannot tell which is which, because both are a directory of files on a disk we do not read.

Most model caches belong under cache/. A twenty-gigabyte set of weights that the image downloads on first run is not worth walking on every backup to store a copy nobody will ever restore.

The part after the first word is a name, not a path — a directory the platform makes for you and keeps across deploys. You cannot name a path on the machine.

An extra cannot see what your site has uploaded. It has its own storage, not the site's. A tool that needs to read your users' files — a thumbnailer, most image tooling — needs storage every machine can reach, which is a different purchase. Ask support before you build around it.

Your own code on an extra

Most of these images want a little of your code: an entrypoint that knows your queue names, a config the tool reads on boot, a short Python shim around a model. Building and publishing a new image every time you edit a ten-line file would defeat the point.

Put those files in .vallic/extra/<slot>/ beside the compose file, and mount them:

.vallic/
  extra/
    voyager.yml          # what runs
    voyager/             # and the files it needs
      rembg/
        entrypoint.py
        settings.toml
services:
  rembg:
    image: ghcr.io/acme/rembg-api:1.4.0
    command: ["python", "/app/entrypoint.py"]
    volumes:
      - code/rembg:/app          # .vallic/extra/voyager/rembg

code/rembg is always .vallic/extra/voyager/rembg — it cannot point anywhere else in your repository. That is deliberate: an extra runs an image you took from a registry, and a mount that could name your repository root would hand your whole codebase to it, committed .env files included.

It comes from your release, not from your branch. An extra runs the code of whatever release your site is running, so a script here and the application that calls it move together. Deploy, and your extra picks up the new files and restarts. An extra that mounts code on an environment you have never deployed has nothing to run yet — deploy first.

It is read-only. A release is replaced whole when you deploy, so anything written there disappears at a moment your container cannot predict. Asking for :rw on a code/ mount is refused rather than quietly ignored; put writable data under keep/ or cache/.

This is for scripts and configuration, not for an application. An extra still has no release of your site, no .env, no cron and no shell — it is something your application calls, and this is the handful of files it needs to answer.

Images, not builds

Name a published image and a version:

image: ghcr.io/acme/rembg-api:1.4.0     # good
image: ghcr.io/acme/rembg-api           # refused — no version
image: ghcr.io/acme/rembg-api:latest    # refused — moves under you

A moving tag is replaced the next time the machine is reconciled, and nothing in your log will explain why the behaviour changed. A digest — image: ghcr.io/acme/api@sha256:… — pins hardest of all.

build: is not supported. These machines build with buildah and have no BuildKit, so a Dockerfile that works on your laptop may not work here; and a build at deploy time on a machine sized for serving is a deploy that looks like it has hung. Build your image in CI, push it to a registry, name the tag.

A private registry needs a login. Add one on your project's Configuration page, under Other → Registry logins: the host as it appears in the image (ghcr.io, registry.example.com:5000), a username, and a password or token. A token that may pull is enough — one that may push is more than this needs.

The login belongs to the project, so every environment of it can pull the same image. Your machines use it to fetch and nothing else: it is never given to a container, never written into your compose file, and never shown back to you once saved. Replacing one means typing it again.

Remove a login and it leaves your machines on their next reconcile.

Credentials

A tool usually wants one: a token for the model it downloads, a key for the bucket it writes to. Do not put it in your compose file — that file is in your repository, and a credential there is a credential in every clone of it.

Name it in vallic.yaml instead, and set the value in the console under Variables:

extra:
  voyager:
    expose:
      rembg: 8001:8000
    env:
      required:
        - REMBG_TOKEN
        - S3_KEY

Your containers get those variables and only those. An extra has no copy of your site's .env: it runs an image we have not read, so what it is given is what you wrote down here, where you can see it in review. Asking for a name you have not set refuses the deploy and says which one, rather than starting a container that cannot work.

Each slot gets its own. A name declared under voyager reaches the voyager machine and no other.

Logs

Whatever your containers print goes where the rest of your project's logs go. If you have pointed the project at a destination of your own, an extra's output arrives there too, stamped with the project and the environment so you can tell it apart. Every machine also keeps its own local copy.

There is no filter on which of your containers are interesting, because on an extra they are all yours. A tool that prints a line per request will send a line per request — if that is more than you want to store, quieten the container.

What your compose file may say

Everything a container needs to run, and nothing that reaches past it:

image, restart, environment, volumes, command, entrypoint, healthcheck, depends_on, user, working_dir, stop_grace_period, tmpfs, shm_size, ulimits, init, and your own x- keys.

Anything else is refused, and the message says why. The ones people reach for most:

Key Why not
ports The platform binds these from your expose block, on the private network
build Extras run published images — see above
networks, network_mode The platform draws the network your environment runs on
env_file, secrets, configs These name files on the machine. Ask for variables by name under env.required instead
privileged, cap_add, devices Nothing on an extra runs privileged or reaches the machine
deploy The platform sets the limits — see what your containers may use, below
container_name, labels The platform names and labels these, and reads its own labels back

At the top level of the file, only services, version and your own x- keys are allowed — top-level volumes, secrets and configs are refused. Volumes use the short form shown above; the long form is refused. A file may declare up to 20 services and be up to 64 KB.

A GPU is not available yet. When it is, it will come with the machine rather than with a line in your compose file.

What it can reach

An extra is there to be called. Your application calls it over the private network; it answers. That direction always works.

Outward, it is closed by default. Its containers may reach the machines running your application — so a tool can call back into your own API when it has finished — and nothing else. Not your database, not your cache or session store, not the internet.

That is deliberate, and it is about what an extra is: an image you chose from a registry, written by somebody else. Everything else on your environment is either our software or code you wrote, and an extra is neither.

If a tool genuinely needs the internet — many download a model the first time they run — say so:

extra:
  voyager:
    expose:
      rembg: 8001:8000
    # rembg fetches its model on first use
    egress: internet

internet is the only thing egress may say. A list of hostnames is not offered: names resolve to addresses that change, and a firewall written from today's answer stops matching tomorrow without telling anyone.

The internet is reached through a proxy. Your containers are given HTTPS_PROXY and HTTP_PROXY, and web requests — HTTP and HTTPS, ports 80 and 443 — go out through them. Most tools and libraries honour those variables without being asked; one that does not, or that needs another port, cannot reach the internet from an extra. Set them in your compose file and they are replaced.

If you leave it out and your container needs it, the symptom is a container that starts and then hangs on its first request out — worth knowing, because it reads like a broken image.

Reaching it from your application

Your application is given the address in its environment, because the address is the one thing you cannot write down yourself — it is assigned when the machine is built.

For the example above:

EXTRA_VOYAGER_HOST=10.16.0.7
EXTRA_VOYAGER_REMBG_PORT=8001
EXTRA_VOYAGER_EASYOCR_PORT=8002

So http://$EXTRA_VOYAGER_HOST:$EXTRA_VOYAGER_REMBG_PORT reaches it. The ports are there so nothing has to be written twice and kept in step by hand; a service that declares several gets the first, and your manifest names the rest.

Nothing on an extra is reachable from the internet, and nothing on it is served through your site's domain.

What your containers may use

The machine is yours, and the platform puts limits on the containers so that one of them cannot take all of it and leave nothing answering.

Memory is divided between the services in your file. One service has the machine's memory; two have half each. This is the one limit that has to be shared out, because a container that runs out of memory is killed — and if the containers could between them ask for more than the machine has, what gets killed is decided by the kernel rather than by which one overran.

CPU is not divided. Every container may use the whole machine, and they share it when more than one is busy. A tool that is working while the others wait gets everything.

Each container may also run 512 processes.

So a second service in your file halves the memory of the first. If you run several, size the machine for their total rather than for the largest — and if one of them wants a lot on its own, it is usually better on an extra of its own than sharing with three others.

You do not write these limits and cannot raise them from the file. If your machine is the wrong size for what you are running, resize it, add another extra on the Resources tab, or ask support.

Knowing it is unwell

The console shows what each of your containers is doing, from the machine's own report: whether it is running, and — if your image declares a healthcheck — whether that check is passing. A container that is running and well is not listed; one that has stopped or is failing its check is.

An image that declares no healthcheck is never reported as healthy. We have not asked it anything, and saying otherwise would be inventing good news.

A container being unwell is not by itself an outage, and we do not treat it as one. Your extra's containers do not fail your deploys — an image we did not write does not get to block your releases — and an extra's own downtime is not counted against your uptime promise.

What does count is your site. Uptime is measured at your hostnames, so if your site starts returning errors because the tool it calls is down, your site is down and that counts. Design for the tool being unavailable: a request that hangs waiting on it is worse for your visitors than a page that renders without whatever it would have added.

Uptime and support

Your uptime SLA does not cover an extra. We cannot promise the availability of a container we did not write and do not update, so an extra's downtime is not counted against the promise — and you are not charged for the SLA on it either. The uptime price is calculated from your other machines.

Worth knowing before it matters: uptime is measured against your site's hostnames. If your site depends on an extra and starts returning errors because that extra is down, your site is down, and that does count. What is excluded is the extra's own availability, not what your application does without it.

Support does cover it, and the support price includes it. If something on an extra needs a person, that is what support is for.

What it does not have

No release of your code, no .env, no cron, and no shell. An extra is not somewhere your application runs — it is something your application calls. Secrets your containers need are yours to put in their environment, or to ask support about; the environment's own variables are not handed to them.