All docs

Dockerfile

When you own the runtime end to end, and the two things Marina still expects from your image.

A Dockerfile at the root of your repo is used automatically. Nothing is detected, nothing is configured, and none of the per-language handling applies: your image is your business.

marina vessels create api --repo acme/api

Reach for this when you need a specific runtime version, a system library, a compiled extension, or exact control over which binary runs. Skip it when you just want a normal app to work, because the automatic path is fewer things to maintain.

What Marina still expects

Listen on PORT. Marina sets it and routes to it. EXPOSE is documentation and does not do the job; your process has to read the variable and bind 0.0.0.0.

CMD ["sh", "-c", "exec ./server --port ${PORT:-3000}"]

Exit on failure, loudly. A container that exits is restarted; one that keeps exiting is marked failed. Both are better than a process that swallows a startup error and sits there, which looks healthy and serves nothing.

That is the whole contract.

A Dockerfile somewhere else

[build]
dockerfile = "docker/Dockerfile.prod"

Build arguments

[build.env] in marina.toml arrives as build arguments. Declare a matching ARG to use one:

[build.env]
VITE_API_URL = "https://api.example.com"
ARG VITE_API_URL
RUN npm run build

That file is committed, so it holds things that end up public anyway. A secret needed at build time does not belong there, and a secret needed at run time belongs in marina env set, which your image reads from the environment like any other variable.

Already-built images

If your image is built somewhere else and pushed to a registry, skip the repo entirely:

marina vessels create api --image ghcr.io/acme/api:1.4

Nothing is built and the image runs as-is, so what you tested is exactly what runs. Marina resolves the tag to the exact image at create, so a tag repointed later cannot change what is already running underneath you. Deploy again to pick up a new one.