Skip to main content

Runtime and Docker

Docker is Luminesk's only runtime driver. The recipe describes a portable container intent; luminesk.lock binds its image to an immutable repository digest, and lifecycle commands translate the manifest into argument-only Docker commands.

Image resolution​

[runtime]
image = "eclipse-temurin:21-jre"
command = ["java", "-jar", "server.jar", "nogui"]

The manifest may use an image tag or a valid repository@sha256:<64-lowercase-hex> reference. During connected locking, Luminesk inspects the local image, pulls it if no repository digest is present, and records a full digest reference. Apply and start use only the lock's image; a tag-only lock entry is invalid.

Image resolution therefore needs both the Docker CLI and a reachable daemon. nesk doctor checks both; docker version prints their detailed versions.

The target is the Linux OS and architecture reported by the active Docker Engine (docker info --format '{{.OSType}}/{{.Architecture}}'), rather than the host OS or CPU. Windows 11 with Docker Desktop's WSL2 Linux backend can therefore use linux/amd64 or linux/arm64 recipes. Luminesk checks the recipe's supported platforms and selects sources for that engine architecture. It does not infer emulation support or silently change an existing lock's target. Frozen installs, updates and startup reject a mismatched engine/context. Pull, build and run explicitly receive the selected platform.

A digest already supplied by a recipe is preserved without a registry lookup during locking. Before startup, Luminesk verifies the local pinned image and explicitly pulls it when absent or for the wrong architecture. The subsequent docker run --pull=never cannot hide a second download. Interactive progress distinguishes engine connection, image inspection, pull, layer download, extraction, container startup and readiness. It shows completed layers and per-layer bytes only when Docker reports them; it does not invent percentages. JSON and non-interactive output remain free of progress events.

Engine metadata/inspection has a 30-second deadline; a pull has a 30-minute deadline with no automatic retry. Container startup has a 60-second deadline, and bounded log retrieval has 30 seconds. Stop allows the recipe's stop_timeout plus 30 seconds for the engine response. Timeout errors identify the operation; registry/DNS/access/platform errors give sanitized diagnostics. Check Docker Desktop/WSL2, the selected context, proxy/DNS and registry login as indicated. Ctrl+C terminates the local operation; interrupted startup also attempts to remove its newly created container. A daemon that is unreachable can prevent cleanup, so inspect the named instance before retrying.

Command and working directory​

[runtime]
image = "eclipse-temurin:21-jre"
command = ["java", "-Xmx4g", "-jar", "server.jar", "nogui"]
workdir = "/server"

command is a non-empty argv array appended after the image. Luminesk does not insert sh -c, so "server.jar; rm -rf /" would be one literal argument, not two commands. Environment-variable interpolation and shell expansion are not performed. Only declared ${input.NAME} placeholders are substituted.

workdir defaults to /server and is passed to Docker. Use an absolute path that exists in the image or under a declared mount.

Writable data and mounts​

[runtime]
image = "eclipse-temurin:21-jre"
command = ["java", "-jar", "server.jar", "nogui"]
workdir = "/server"
read_only_root = true

[[runtime.mounts]]
source = "."
target = "/server"
mode = "rw"

read_only_root defaults to true. Writable server state must therefore live under a read-write bind mount.

Mount source is a portable path resolved inside the instance root; escape via .., an absolute path, or a symlinked resolution is rejected. A non-dot source is created as a directory before Docker starts. target is passed to Docker as the bind destination; use an absolute container path. mode is rw by default or ro for a read-only bind.

When no mounts are declared, Luminesk implicitly mounts the entire instance root at workdir read-write. As soon as one mount is declared, that implicit mount disappears; list every path the container needs.

Use Ownership and User Data to keep world/, plugins/, and mutable configuration safe across package updates. Docker mount mode and Luminesk file ownership solve different problems.

On Windows, absolute drive paths, spaces and Unicode are passed as a single --mount argument; CSV fields are quoted where necessary. Docker Desktop translates the host path into its Linux VM. The Windows account running Docker must have access to the instance folder, and Docker Desktop must be allowed to share it. Luminesk does not change host ACLs or make the container privileged.

Ports​

Java servers normally publish TCP; Bedrock servers normally publish UDP:

[[runtime.ports]]
name = "java-game"
host = 25565
container = 25565
protocol = "tcp"

[[runtime.ports]]
name = "bedrock-game"
host = 19132
container = 19132
protocol = "udp"

Host and container values must resolve to 1–65535. They may be integers or a whole input reference such as ${input.port}. Protocol defaults to tcp and may be tcp or udp.

The schema has no host-IP field: Luminesk passes HOST:CONTAINER/PROTOCOL to Docker. Control external exposure with Docker/host firewall configuration and verify it explicitly; do not assume a published management port is local-only.

User and resources​

[runtime]
image = "eclipse-temurin:21-jre"
command = ["java", "-jar", "server.jar", "nogui"]
memory = "4g"
run_as = "1000:1000"

memory is optional. After input interpolation it must be a positive integer with optional b, k, m, or g suffix and is passed as Docker's memory limit. Coordinate it with a JVM heap setting so the process has room for non-heap memory.

run_as is passed to Docker's --user after declared input interpolation. An official recipe can expose runtime_uid and runtime_gid inputs and use run_as = "${input.runtime_uid}:${input.runtime_gid}". The recipe/operator must ensure that the identity can read managed content and write mounted data. Files created by a container without run_as may be owned by root on the host.

For a Linux container on Windows, these are container Linux UID/GID values, not Windows account IDs. Keep Lumi's default 1000:1000 unless your deployment requires another Linux identity. Windows-backed mounts use Docker Desktop's file-sharing/Windows permission model; a directory inside WSL's Linux filesystem uses Linux ownership. Check that the selected identity can write both /server and /tmp. Do not substitute a Windows SID or remove --user to bypass an access problem. See Docker's Windows permissions and bind mount documentation.

Lumi publishes 19132/udp and checks readiness using its startup log message. A successful log check does not prove that Windows Firewall permits inbound game traffic; verify UDP access from the intended client separately.

Manifest schema v1 has no runtime CPU field. [build].cpu limits recipe builds, not the server container. Apply external Docker controls if a production host needs CPU policy beyond the public runtime schema.

Restart and shutdown​

[runtime]
image = "eclipse-temurin:21-jre"
command = ["java", "-jar", "server.jar", "nogui"]
restart = "unless-stopped"
restart_limit = 0
stop_signal = "SIGINT"
stop_timeout = 30

Restart values are no (default), on-failure, always, and unless-stopped. A positive restart_limit is appended only to on-failure. stop_signal defaults to SIGINT; Docker receives it as the container stop signal. nesk stop waits stop_timeout seconds (default 30, minimum 1).

Choose the graceful signal expected by the server implementation and test both normal stop and update restart. A Docker restart policy is not a substitute for Luminesk readiness or transactional rollback.

Lifecycle​

nesk start --dir ./instance
nesk status --dir ./instance
nesk logs --dir ./instance
nesk logs --dir ./instance --tail 500 --since 10m --timestamps
nesk logs --dir ./instance --follow
nesk restart --dir ./instance
nesk stop --dir ./instance

start verifies installed state/recipe/lock, removes a stale same-name container, starts the locked image, and waits for checks. status reconciles state with Docker. restart performs stop then start. attach opens an interactive full-screen console with bounded recent history, live output, server command input, and explicit detach/stop/kill shortcuts. It does not support JSON/non-interactive operation.

A start readiness failure stops and removes the new container. An update of a previously running instance adds a larger safety envelope: it stops the old container, installs the candidate, starts/checks it, and restores/restarts the previous package on failure.

See Checks and Readiness for every check kind and phase.

Dockerfile builds​

Some recipes declare an isolated package build:

[build]
file = ".luminesk/Dockerfile"
output = "/out"
timeout = 1200
cpu = 2
memory = "2g"
network = false

There is no [permissions] or [build.permissions] table. The presence of [build] opts into Dockerfile execution. network = false is the default and uses --network none; setting it to true gives the build Docker's default network and expands trust.

Every external FROM image is parsed before build, resolved to a repository digest, stored under lock.build.images, and substituted into a temporary Dockerfile. Dynamic base expressions are rejected. Syntax frontends, if used, must already be digest-pinned.

The context is bounded, excludes repository/runtime state directories, rejects links and special files, and receives declared timeout/CPU/memory/network limits. Luminesk creates a container from the build image and copies only build.output into package staging. It never runs a recipe command directly on the host.