Skip to content

Custom runtime images

Sometimes the Envoryx image is one package short. Your PDF library wants Ghostscript, a PHP extension isn’t in the list, or the team already has a hardened base image. For that, a runtime can run an image of your own instead of the Envoryx one.

You set it in the project’s Runtime section, on the Runtime images card at the bottom. It has a row for each runtime the project has (PHP, Node.js, Python, Go, Ruby, Java, .NET), and the Image field offers three choices: Envoryx image (the default), Image from a registry and Dockerfile in the project. Databases, the other services and the web server always keep the Envoryx images. Workers and cron jobs run in the image of their runtime, so they follow your choice.

Put any reference into Image reference, like ghcr.io/acme/php:8.4, and save. Envoryx pulls it right then, so a tag that moved is picked up, and every Restart pulls it again, just like the Envoryx images. If the project is running, its containers are recreated with the new image straight away; otherwise it applies at the next start.

This is the one I’d reach for first, because the Dockerfile lives in the repository next to the code and the whole team gets the same image. Give the path relative to the project directory; the field suggests .envoryx/php.Dockerfile (or node, python, … for the other runtimes). Envoryx builds it and tags the result envoryx-build/<runtime>:<hash>.

Start from the Envoryx image of your version, then you keep everything Envoryx relies on (Composer, Xdebug, mail to Mailpit and so on) and only add what’s missing:

.envoryx/php.Dockerfile
FROM ghcr.io/envoryx/envoryx-php:8.4
RUN apt-get update && apt-get install -y --no-install-recommends ghostscript \
&& rm -rf /var/lib/apt/lists/*
COPY conf/ /usr/local/etc/php/conf.d/

The directory of the Dockerfile is the build context, and all of it goes to Docker. That’s why the Dockerfile wants a directory of its own like .envoryx/ rather than the project root: a context with more than 5000 files or 512 MB is refused. Symbolic links in it are left out, and .dockerignore isn’t read. Envoryx builds with Docker’s classic builder, so BuildKit-only syntax like RUN --mount=… or heredocs won’t work.

The tag’s hash covers the Dockerfile and every file next to it. Change one of them, and the next start or Restart builds a new image; leave them alone, and nothing is built twice. A restart builds before it stops anything, so a broken Dockerfile leaves the running project as it is. A failed build keeps your setting and shows its output under the row, open, with a build failed badge, so you fix the file and try again. Projects with the same context share the image, and a branch environment builds the Dockerfile of its own branch.

Rebuild without cache builds again with freshly pulled base images and without Docker’s build cache. You need it when nothing in your context changed but the world did: a new Envoryx image under the same tag, or an apt-get that should fetch newer packages.

Envoryx expects a few programs in a runtime image, so it checks every image when you set it and after every build. It runs the image once with sh -c and looks for:

Runtime Needed Missing means no …
all sh git (git in the terminal, packages from git repositories), socat (waiting for the database at start, SSH port forwarding), ssh (git over SSH with the deploy key), rsync (rsync over SSH)
PHP php, php-fpm composer (Composer installs), msmtp (mail() to Mailpit), the Xdebug extension (step debugging)
Node.js node, npm corepack (pnpm and Yarn)
Python python3 (or python) pip (pip installs), uv (uv projects)
Go go air (live reload), dlv (debugging), gotestsum (test reports)
Ruby ruby, bundle rdbg (debugging)
Java java mvn, gradle (Maven and Gradle projects without a wrapper)
.NET dotnet netcoredbg (debugging), dotnet-ef (EF Core migrations)

It also looks at the image itself. An ENTRYPOINT other than tini, dumb-init or the official images’ entrypoints is named, because it runs before every command Envoryx starts and has to end with exec "$@". A PHP image that isn’t built from the Envoryx PHP image gets told that the extension switches in the PHP card have no effect on it, and an image based on the Envoryx image of another runtime is pointed out too.

What’s missing shows up as The image lacks what Envoryx expects, each line saying what won’t work, for example socat is missing: no waiting for the database at start and SSH port forwarding. It’s a warning, not a refusal: Envoryx uses the image anyway. When nothing is missing, the row says everything Envoryx needs is there.

For an image behind a login, an instance admin adds the registry under Settings → Private registries with Add login: the host (ghcr.io, registry.example.com:5000, or docker.io for Docker Hub), a username and a password or token. For GHCR that’s a token with read:packages. Envoryx uses the login for pulls and hands it to builds for their FROM images, so a Dockerfile can start from a private base image too. The password is never shown again; saving the card without one keeps the stored one. If you set an image that can’t be pulled for lack of a login, the error points you to this card. Once the login is there, save the same image on the project again: Envoryx then pulls it, checks it and puts it in place. A Restart does the same.

Setting an image or going back to the Envoryx image needs the admin role in the project, like changing a runtime. Developers can Rebuild without cache. Built images nobody uses any more show up on the Docker page with the other unused images; images from your registries are never removed by Envoryx.

In envoryx.yml each runtime takes image: or dockerfile:, never both:

php:
version: "8.4"
dockerfile: .envoryx/php.Dockerfile
node:
version: "24"
image: ghcr.io/acme/node-tools:24

Git operations and templates keep using the Envoryx image, by the way. They need its tools, and a Dockerfile from the repository doesn’t exist before the clone.