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 directory | What Elula does |
|---|---|
Dockerfile | Builds with your Dockerfile |
package.json | Builds a Node.js image (server or static site, see below) |
Cargo.toml | Builds a Rust release binary into a slim Debian image |
requirements.txt, pyproject.toml, go.mod, pom.xml, Gemfile or composer.json | Builds with Google Cloud Buildpacks (gcr.io/buildpacks/builder:google-22) |
| Anything else | Serves 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:
- Installs dependencies with the package manager it finds:
pnpmif there is apnpm-lock.yaml,yarnif there is ayarn.lock, otherwisenpm. - Runs
prisma generateif@prisma/clientis a dependency. - Runs your
buildscript if there is one. - Picks how to run the app:
| Your project has | The app starts with |
|---|---|
A start script | npm run start (or the yarn / pnpm equivalent) |
No start script, a server framework (Express, Fastify, Koa, Hono, NestJS) and a dev script | npm run dev |
No start or dev script, a server framework, tsconfig.json and a server.ts, index.ts, app.ts or main.ts | Compiles with tsc, then runs the compiled file from your outDir (default dist) |
| Nuxt | node .output/server/index.mjs |
| None of the above | Static 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.
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 avite.config.*filebuild/if the project usesreact-scriptsdist/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.
| Preset | Effect |
|---|---|
auto (default) | Detection as described above |
static | Skip runtime detection and serve the files with nginx (a Dockerfile, if present, still wins) |
dockerfile | Require 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 withARGbefore 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:
8080by default, or the port you set. Bind to0.0.0.0and readPORTif 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.