Skip to content

Deploy an app

Package a JavaScript or TypeScript server in a container image on elide.dev/elide, with environment, sandbox flags, logs and a health check.

On this page

This page packages a TypeScript server as a container image on elide.dev/elide.

Write the app

// server.ts
import { Hono } from "hono";

const app = new Hono();

app.use(async (c, next) => {
  await next();
  console.log(JSON.stringify({ method: c.req.method, path: c.req.path, status: c.res.status }));
});

app.get("/healthz", (c) => c.text("ok"));
app.get("/", (c) => c.json({ greeting: process.env.GREETING ?? "hello" }));

export default app;
// healthcheck.mjs
const res = await fetch("http://127.0.0.1:8080/healthz");
process.exit(res.ok ? 0 : 1);
{
  "name": "deploy-app",
  "type": "module",
  "dependencies": {
    "hono": "^4.7.0"
  }
}

Run elide install --ecosystems=npm once to write aube-lock.yaml.

Write the Dockerfile

FROM elide.dev/elide:1.6.0
WORKDIR /app
COPY package.json aube-lock.yaml ./
RUN elide install --ecosystems=npm
COPY . .
EXPOSE 8080
HEALTHCHECK --interval=10s --timeout=3s CMD ["elide", "healthcheck.mjs"]
CMD ["--allow-read=/app,/root/.cache/aube", "--allow-net=api.example.com", "serve", "--no-tui", "--host", "0.0.0.0", "--port", "8080", "server.ts"]
# .dockerignore
node_modules
.env
LineWhy
FROM elide.dev/elide:1.6.0The image’s entrypoint is elide, so CMD holds only its arguments. Tags: Container Images.
RUN elide install --ecosystems=npmInstalls inside the image. node_modules links into /root/.cache/aube, so a host node_modules would not work.
--host 0.0.0.0A default-exported handler binds 127.0.0.1, which is unreachable from outside the container.
--no-tuiWithout it, docker run -t starts the terminal UI and the app’s output never reaches docker logs.
HEALTHCHECKThe image has no curl or wget; a short Elide script does the check.
docker build -t my-app .
docker run -d --name my-app -p 8080:8080 -e GREETING=hi my-app
curl http://127.0.0.1:8080/
{"greeting":"hi"}

docker inspect --format '{{.State.Health.Status}}' my-app prints healthy once the first check passes. docker stop ends the server within a second, with exit code 143.

Pass environment variables

process.env holds the container’s variables; pass them with docker run -e NAME=value or --env-file .env. Elide does not read .env files or set NODE_ENV. For local runs, load the file in code:

import { existsSync } from "node:fs";

if (existsSync(".env")) process.loadEnvFile();

process.loadEnvFile() without the check throws ENOENT: no such file or directory, open '.env' when the file is missing. See Environment Variables.

Restrict the server

Programs run unrestricted until a sandbox flag is given. Grant a server what it needs and nothing else:

FlagIn the Dockerfile above
--allow-read=PATHS/app and the npm store /root/.cache/aube.
--allow-net=HOSTSOutbound hosts. Listening on a port needs no grant.
--allow-run=CMDSNot given: subprocesses stay allowed. Give a list to restrict them.
--deny-net=HOSTSHosts to block even when allowed, e.g. --deny-net=169.254.169.254.
--max-mem=SIZE, --timeout=DURATIONHeap size and total run time.

Global flags go before the command (serve, run). --deny-net and --deny-run need the = form. Leaving the npm store out of --allow-read stops the server at startup:

Error: Filesystem read denied by policy: /app/node_modules/hono

A blocked outbound call names the flag:

fetch to '169.254.169.254:80' denied by --deny-net

Every flag: Sandbox and Limits.

Read logs

console.log writes to stdout and console.error to stderr; docker logs shows both:

{"method":"GET","path":"/healthz","status":200}
{"method":"GET","path":"/","status":200}

elide serve --no-tui prints no access log, and a handler that throws answers 500 without printing the error. Log requests and errors in the app, or export request spans, metrics, and errors with OpenTelemetry.

Deploy a JVM app

Was this page helpful?