Skip to content

Deploying

Frameworks and Dockerfiles

How Elula decides to build your app, what it generates for you, and when to bring your own Dockerfile.

Most apps deploy without a Dockerfile. At build time Elula looks at the files in the app's directory (the repository root, or the root directory you set) and picks a build method. If you add a Dockerfile, Elula always uses it.

How detection works

Elula checks these in order and uses the first match:

Found in the app directoryWhat Elula does
DockerfileBuilds with your Dockerfile
package.jsonBuilds a Node.js image (server or static site, see below)
Cargo.tomlBuilds a Rust release binary into a slim Debian image
requirements.txt, pyproject.toml, go.mod, pom.xml, Gemfile or composer.jsonBuilds with Google Cloud Buildpacks (gcr.io/buildpacks/builder:google-22)
Anything elseServes the files as a static site with nginx

The dashboard runs the same checks when you create an app and shows the result (framework, build command, start command, output directory, port) before you deploy. It is a preview: the real decision is made again at build time.

Node.js apps

For a package.json app, Elula generates a Dockerfile based on node:lts-alpine:

  1. Installs dependencies with the package manager it finds: pnpm if there is a pnpm-lock.yaml, yarn if there is a yarn.lock, otherwise npm.
  2. Runs prisma generate if @prisma/client is a dependency.
  3. Runs your build script if there is one.
  4. Picks how to run the app:
Your project hasThe app starts with
A start scriptnpm run start (or the yarn / pnpm equivalent)
No start script, a server framework (Express, Fastify, Koa, Hono, NestJS) and a dev scriptnpm run dev
No start or dev script, a server framework, tsconfig.json and a server.ts, index.ts, app.ts or main.tsCompiles with tsc, then runs the compiled file from your outDir (default dist)
Nuxtnode .output/server/index.mjs
None of the aboveStatic site: the build output is served by nginx

Server images set PORT=8080. Read PORT in your server code. See Deploy a web app or API.

Note: Only start counts as a server command. Scripts named serve or preview (for example vite preview) are ignored, and the app is treated as a static site.

Static Node builds (React, Vue, Vite)

When there is no server command, Elula runs your build and serves the output with nginx on port 8080:

  • dist/ if there is a vite.config.* file
  • build/ if the project uses react-scripts
  • dist/ otherwise

Unknown paths fall back to index.html, so client-side routing works.

Next.js with Prisma

If a Next.js app uses Prisma, Elula builds with --experimental-build-mode compile, so the build doesn't try to reach the database while prerendering. A placeholder DATABASE_URL is set during the build only.

Static sites without package.json

A folder of HTML files is served by nginx on port 8080. Elula picks the entry page in this order: index.html, index.htm, then the first .html file alphabetically. With no HTML file, nginx lists the directory.

If the folder has no .dockerignore, Elula adds one that keeps .git, .env files and node_modules out of the image.

Framework presets

You can override detection with a preset: elula init --framework static, or the Framework field when creating an app in the dashboard.

PresetEffect
auto (default)Detection as described above
staticSkip runtime detection and serve the files with nginx (a Dockerfile, if present, still wins)
dockerfileRequire a Dockerfile. The build fails with a clear error if there isn't one

The dashboard also lists Next.js, React, Python and Go. These build the same way as auto. For Next.js, React and Vue apps whose framework is set explicitly, Elula also saves the app's URL after a deploy as NEXT_PUBLIC_URL (Next.js) or VITE_PUBLIC_URL (React and Vue), and passes it into the next build.

Build-time variables

Variables whose names start with NEXT_PUBLIC_, VITE_, REACT_APP_ or NUXT_PUBLIC_ are passed into the build, because these frameworks bake them into the client bundle. Set them before you deploy:

elula env set NEXT_PUBLIC_API_URL=https://api.example.com
elula deploy

NEXT_PUBLIC_, VITE_ and REACT_APP_ variables are build-time only. NUXT_PUBLIC_ variables are also set at runtime, because Nuxt reads them on the server.

  • In generated Node.js builds this works automatically.
  • With your own Dockerfile, the values arrive as --build-arg. Declare each one with ARG before the step that needs it.
  • Buildpacks builds don't receive them.
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
RUN npm run build

Bring your own Dockerfile

Add a Dockerfile to the app's directory when detection can't handle your build: system packages, an unusual start command, a language Buildpacks doesn't pick up the way you want.

Rules:

  • The container must listen on the port Elula expects: 8080 by default, or the port you set. Bind to 0.0.0.0 and read PORT if you can.
  • A service must answer HTTP. A job just runs and exits (exit code 0 means success).
  • In a monorepo, put the Dockerfile in the app's subdirectory, for example apps/web/Dockerfile.

A minimal example:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD exec gunicorn --bind 0.0.0.0:$PORT main:app

Build behaviour

  • Layer cache. Each build pulls the previous image as a cache, so unchanged layers are not rebuilt.
  • Same commit, no rebuild. If an image for the exact commit already exists, Elula skips the build and deploys that image.
  • Time limits. A build that hasn't started after 10 minutes, or hasn't finished after 15 minutes, is cancelled and the deployment fails with the reason.

Read build output with elula logs, or the Logs tab with build selected.