03.1 Building your own image

Chapter 03.1

Building your own image

When none of the runtimes on offer fits, an administrator can upload one. An image is a Dockerfile and a small manifest in a zip; the platform builds it on the box and it appears in the site type picker beside the built-in ones. Nothing about the base is restricted or inspected — which is exactly why the few things the platform does expect are yours to get right.

This is an administrator's chapter. Site Images is not in a site owner's menu, and uploading is not something a site owner can be granted: building an image runs the author's instructions on the box. If you are a site owner who needs a runtime nobody offers yet, read on anyway — it tells you what to ask for.

Where it is

The panel's Site Images page, which used to be called Catalog, has two sections. Built-in images arrive with the platform — the ones chapter 03 describes. Custom images are the ones somebody uploaded to this box, and the form for adding one sits below their table.

The two kinds are offered identically when a site is created. Nothing in the picker says where an image came from, and nothing in a site behaves differently because of it.

What the platform expects of an image

The platform builds what you send and does not look inside it. There is no check on your base, and no check that you have understood any of this — so an image that ignores the list below builds perfectly and then fails to serve. That is worth reading twice, because it is the failure people actually hit and the build log looks entirely healthy on the way to it.

Serve HTTP on port 80
Plain, unencrypted HTTP inside the container. Certificates and TLS belong to the edge proxy and are not your image's business — chapter 07 covers what that arrangement means for the application later.
Answer the health check
The platform asks the container whether it is up, and a container that never answers is treated as one that failed to start. The starter build context below already answers it; if you build from something else, carry that part across rather than rediscovering it.
Read your settings from the environment
Everything the site's creator can configure reaches the container as an environment variable, and only what your manifest declares reaches it at all. Nothing is passed through that the image did not ask for.
Serve out of the site's folder
A catalog site's folder under /var/www/virtual/ is mounted into the container at the identical path (chapter 01). That folder, not the image, is where the site's code lives. Your image decides which directory beneath it is the docroot — the built-in images differ on exactly this point — so decide, say so in the description, and stay with it.
The application is not part of the image

An image is a runtime. It is built once and shared by every site created from it, and each of those sites gets its code the ordinary way, through its own deploy (chapter 04).

Building an application into an image hands every site made from it the same frozen copy, with no way to update one without rebuilding for all of them. The size limits further down are set at a build context rather than an application, and they are the practical reminder of this.

The manifest

nimbus-image.json sits in the archive beside the Dockerfile and describes the image to the panel. It carries four things: the display name shown in the site type picker, the description under it, whether the image reads an .env file from the site's folder, and the settings the image accepts.

{
  "displayName": "My Legacy App",
  "description": "PHP 7.4 with the extensions this application needs. Docroot: public/",
  "envFile": true,
  "settings": []
}

envFile decides what a site built on this image is offered. Declare it true and those sites get the Environment tab, which edits the .env in the site's folder (chapter 02), and the Databases tab, which provisions a MySQL database and user for the site (chapter 06). Leave it out and they get neither. It is optional and defaults to false, which is the right answer for an image that only serves static files.

The built-in Laravel image declares it too. That is the whole of the rule: the panel recognises no image by name, so whatever you call yours, at whatever version, the tabs follow what the manifest says.

Forgetting envFile costs you a version

An application image that omits it builds, serves and looks entirely correct — and then the Environment tab is not there when you go to put your database credentials in. Nothing has failed and no message appears, because your manifest never said the image reads a .env.

The fix is one line, but no part of an uploaded image can be edited, so it means uploading a new version rather than correcting the one you sent. Get it right on the first upload.

Declaring no settings is a perfectly good answer — two of the built-in images declare none. A setting is worth adding when a site genuinely needs to differ from another site on the same runtime, and it is worth knowing that one setting has three separate lives before you declare it.

You, writing the imageWhoever creates the siteThe running container
Declare it in the manifest, with a default that works. Meets it as a field on the New site form, and as a row on the site's General tab afterwards. Receives it as an environment variable and reads it at start.
Name it for the person filling it in, not after your config file. Changing one recreates the container — the folder, domain, certificate and database all survive that. Gets nothing else. An undeclared key is rejected rather than passed through.

The description is where the reader learns what the docroot is, what the image has installed, and what it will not do. It is the only documentation a site creator gets, so write it for somebody choosing between two entries in a dropdown.

Start from the starter

The add form offers a starter build context to download: a zip holding a Dockerfile, a nimbus-image.json, a README.md and a site/ directory, all at its top level. It is generated from the platform's own sources at the moment you ask for it, so it always matches what the platform currently expects.

Take it. It is not an optional extra but the shortest path to an image that works: everything above is already satisfied in it, so your job becomes adding to a working image rather than deducing one. Its README is the short form of this chapter, and the built-in images are the fuller worked examples if a minimal one is not enough.

Adding one

Open Site Images and fill in the form beneath the custom images table.

Name
What this image is called on this box. It cannot be the name of a built-in image — those are reserved.
Version
Typed here rather than declared in the zip, because it identifies this particular upload rather than the image in general. It has to be usable as an image tag, so characters a tag will not take are refused. latest is refused too, deliberately: it names something that can change underneath you, and nothing here is allowed to.
The archive
One zip: the Dockerfile, the nimbus-image.json, and whatever else the build needs — a config file, an entrypoint script, a template. At most 16 MB zipped, at most 2,000 files, and at most 64 MB once unpacked. Those are roomy for a build context and deliberately tight for anything larger, because a build context is configuration and the application arrives through the site's own deployment.

The build runs without you

Uploading starts a build and hands the page straight back. You do not have to wait for it or keep the tab open — progress is reported the way the panel reports any long job, and dismissing that report does not stop the build.

There is no time limit either. A build that has been running a while is a slow build, not a failed one, and the panel says as much rather than giving up on it.

An image is only offered once it has built

A custom image is listed the moment you upload it, but it cannot be chosen when creating a site until its build has succeeded. One that is still building, or that failed, stays listed and stays unselectable.

Built-in images behave the same way on a box where they have not been built yet, so this is not a rule for custom images — it is the rule for images.

Nothing about an image can be edited

There is no Edit button on a custom image, for anything: not the archive, not the display name, not the description, not the settings it declares. An image is a dependency of every site running on it, and one that could change in place would change what those sites restart onto, with no record of what they used to have.

So you change an image by uploading a new version of it — same name, different version. Both stay listed, both are selectable, and every existing site stays on the version it was created with until somebody moves it.

Moving a site across is the ordinary site type change from chapter 03: versions of one custom image appear in that picker as separate choices, and settings carry over, take a default or are dropped exactly as described there. A site moves back the same way, which is what makes trying a new version safe.

The three row actions

Rebuild
Builds the same archive again under the same name and version. Use it when a build failed, or when the built image has gone missing from the server. It can never change what the image is: the archive it builds is the one you uploaded.
Build logs
The output of the most recent build and no other — earlier logs are not kept. A long log keeps its end rather than its beginning, because the end is where the failure is.
Delete
Asks for confirmation, and is refused while any site is on that exact version. The refusal names the sites, so you know what has to move first. Other versions of the same name are untouched.

What gets refused

Almost everything is caught before anything is stored, and the message names what was wrong. The one thing that is never caught is the one this chapter opened with.

What you sentWhat happens
A name and version that already existRefused — versions are unique per name. Pick another version; what is stored is never overwritten.
latest as the versionRefused, on purpose.
A version an image tag would not acceptRefused on the field.
A name a built-in image already usesRefused. Built-in names are reserved.
An archive with no Dockerfile or no manifest, or a manifest that will not parseRefused, naming what was wrong. Nothing is stored.
An archive over the size, file count or unpacked limitsRefused against the limits above, before anything is unpacked.
An archive that builds and failsThe image is listed as failed and the reason is in Build logs. Fix the archive and upload a new version — the failed one cannot be replaced.
A build the server restarted in the middle ofThe image comes back marked failed, with the restart named as the reason. Press Rebuild; nothing is lost.
An image that builds and then will not serveNot refused at all — there is nothing wrong with the archive. This is the contract, at the top of this chapter.