nexos.yaml — AI Agent Spec
This page is written for AI coding assistants. It is the complete, self-contained spec for generating a working nexos.yaml (and, when needed, a Dockerfile.nexos) for any repository. A human reader can use the human reference instead.
Follow the procedure below exactly. Produce two artifacts at most: nexos.yaml (required) and Dockerfile.nexos (only if the repo has no usable Dockerfile). Do not add comments unless they explain a non-obvious choice. Do not invent fields not listed in the schema.
What Nexos does
Nexos turns every git branch into a live preview environment. On each push, it reads nexos.yaml from the commit, builds the Docker image, provisions the managed services declared in the file, starts the containers, and exposes a preview URL at {branch}-{name}.nexos.rs.
Your job is to produce a nexos.yaml that makes the repository run correctly in that environment, plus a Dockerfile.nexos if the existing build does not satisfy the requirements below.
Procedure
- Read the repo. Look at
package.json,pyproject.toml,requirements.txt,go.mod,Cargo.toml,Gemfile,composer.json,pom.xml, framework configs, and any existing Dockerfile. - Detect the stack and port (table below). If the app reads
process.env.PORT/os.environ["PORT"]already, use whatever default the framework uses; Nexos will set$PORTto match theapp.portyou declare. - Detect managed services from imports / connection string references in code (table below). Enable each one in
services:. Do not enable services the app does not use. - Detect migrations. If you find Prisma, Drizzle, Knex, TypeORM, Alembic, Django
migrate, Railsdb:migrate, or similar, set the corresponding command ashooks.on_deploy. - Detect seed scripts. If
package.jsonhas aseedscript or there is a clear one-time seeder, set it ashooks.on_new. - Check the Dockerfile. If there is no Dockerfile, or it fails the requirements below, create
Dockerfile.nexosand pointbuild.dockerfileat it. Otherwise, leavebuild.dockerfileas the existing path. - Write the smallest
nexos.yamlthat works. Skip any field where the default is correct. Do not outputbuildResourcesorapp.cpu/memoryunless you have evidence the defaults are wrong.
Stack and port detection
| Stack | Detect by | Default port |
|---|---|---|
| Next.js | next in package.json deps | 3000 |
| Node/Express/Fastify | express / fastify in deps | 3000 |
| NestJS | @nestjs/* in deps | 3000 |
| Django | manage.py + Django in requirements.txt / pyproject | 8000 |
| Flask/FastAPI | flask / fastapi in requirements.txt | 8000 |
| Rails | Gemfile with rails | 3000 |
| Go (net/http, chi, gin, echo) | go.mod present | 8080 |
| Rust (axum, actix) | Cargo.toml present | 8080 |
| Spring Boot | pom.xml or build.gradle with spring-boot | 8080 |
If the codebase calls app.listen(NUMBER) or equivalent with a literal port, use that number instead of the default. Apps that read $PORT can keep any port — Nexos sets the env var to match app.port.
Service detection
| Code signals | services entry | Auto-injected env |
|---|---|---|
| pg, postgres, prisma, drizzle, knex, sequelize, typeorm, sqlalchemy with postgresql://, psycopg2, pg gem | postgres | DATABASE_URL |
| redis, ioredis, redis-py, redis gem, bullmq, sidekiq | redis | REDIS_URL |
| mongodb, mongoose, pymongo, mongoid | mongodb | MONGODB_URL |
| @elastic/elasticsearch, elasticsearch-py, elasticsearch-rails | elasticsearch | ELASTICSEARCH_URL |
| @aws-sdk/client-s3, boto3 S3, aws-sdk s3, minio | minio | S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY, S3_BUCKET |
| nodemailer, smtplib, mail gem (dev/test config) | mailhog | SMTP_HOST, SMTP_PORT |
The injected env vars above always exist when the corresponding service is enabled — do not add them to the env: block. If the app reads a differently-named variable (e.g. POSTGRES_URL), add an entry in env: that aliases it: POSTGRES_URL: $DATABASE_URL — but only if you verified the app reads the alternate name.
When to create Dockerfile.nexos
Prefer the existing Dockerfile. Only create Dockerfile.nexos if at least one of these is true:
- There is no Dockerfile in the repo.
- The existing Dockerfile hardcodes a port via
EXPOSE+CMDand the app does not read$PORT. - The existing Dockerfile is dev-only (e.g. installs dev dependencies and runs
npm run dev, mounts source as a volume, depends on docker-compose links). - The existing Dockerfile assumes services on
localhostrather than using injected env vars. - The existing Dockerfile fails to install production dependencies or omits the build step.
Name the file exactly Dockerfile.nexos, place it at the repo root, and set build.dockerfile: ./Dockerfile.nexos in nexos.yaml.
Dockerfile.nexos requirements
- Multi-stage when the language has a build step (Node, Go, Rust, Java, .NET). Final stage must be slim/alpine and contain only runtime artifacts.
- Production dependencies only in the final stage (e.g.
npm ci --omit=dev,pip install --no-cache-dir). - Do not hardcode the port. The container must listen on
$PORT. UseCMD ["sh", "-c", "..."]if the runtime cannot expand env vars on its own. - Do not assume localhost services. Read
$DATABASE_URL,$REDIS_URL, etc. — Nexos injects them. - Use a non-root user when the base image makes it straightforward (Node alpine:
USER node). - Order layers by stability (deps first, source last) so BuildKit caching helps on incremental builds.
Example Dockerfile.nexos (Node/Next.js)
# Dockerfile.nexos
# Multi-stage Node.js example. Adapt for your stack.
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.next ./.next
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
# Nexos sets $PORT — do not hardcode it.
EXPOSE 3000
CMD ["sh", "-c", "node server.js"]
Complete schema
Every field below is optional. Output only the fields you need; omit the rest. Values shown are the defaults.
# nexos.yaml — full schema (every field is optional)
name: string # project display name
build:
dockerfile: ./Dockerfile # path; use ./Dockerfile.nexos if you create one
context: .
args: # plaintext build args (NOT for secrets)
KEY: value
app: # the main runtime container
port: 3000 # injected as $PORT
cpu: 1 # cores, 0.1–16
memory: 512 # MB, 64–65536
buildResources: # limits during image build
cpu: 2 # cores, 0.5–16
memory: 2048 # MB, 256–65536
timeout: 600 # seconds, 60–3600
services: # managed addons (one per environment)
postgres: true | false | { enabled, version, storageMb }
redis: true | false | { enabled, version }
mongodb: true | false | { enabled, version, storageMb }
elasticsearch: true | false | { enabled, version, storageMb }
minio: true | false | { enabled, storageMb }
mailhog: true | false
hooks:
on_deploy: string | null # runs every deploy (e.g. migrations)
on_new: string | null # runs once on brand-new environments
env: # shared env vars (encrypted at rest)
KEY: value # string | number | boolean
containers: # extra containers (workers, sidecars, jobs)
- name: lowercase-with-hyphens # required
dockerfile: ./path/Dockerfile
context: .
port: 3001
cpu: 0.5
memory: 256
public: false # true -> own preview subdomain
command: "node worker.js" # overrides image CMD
type: service | job # job = runs once per deploy
depends_on: [other-container]
default: false # true -> preview URL routes here
run_hooks: false # true -> hooks run in this container
env:
KEY: value
nodePool:
type: shared | private # default: shared
Constraints and rules
- Strict schema. Unknown top-level fields are rejected. Do not invent fields.
- No secrets in committed YAML. Put API keys, OAuth secrets, database passwords for external services, etc. in the dashboard (encrypted at rest). The
env:block in the committed file is for non-secret config (NODE_ENV,LOG_LEVEL, feature flags). - Do not duplicate auto-injected env vars.
DATABASE_URL,REDIS_URL,PORT, and the other service URLs are set automatically. - Container names in
containers:must match^[a-z0-9-]+$. - At most one entry in
containers:may setdefault: true, and at most onerun_hooks: true. - Type=job containers must declare
command. - Resource ranges:
cpu0.1–16,memory64–65536 MB,port1–65535,buildResources.timeout60–3600 seconds.
Decision examples
Next.js app with Prisma + Postgres + Redis cache
name: my-app
build:
dockerfile: ./Dockerfile
app:
port: 3000
services:
postgres: true
redis: true
hooks:
on_deploy: npx prisma migrate deploy
Django app, no existing Dockerfile
Create Dockerfile.nexos using the official python:3.12-slim base, install requirements.txt, run gunicorn project.wsgi --bind 0.0.0.0:$PORT. Then:
name: my-django-app
build:
dockerfile: ./Dockerfile.nexos
app:
port: 8000
services:
postgres: true
hooks:
on_deploy: python manage.py migrate --noinput
on_new: python manage.py loaddata fixtures/seed.json
Node API with a separate worker
name: my-api
app:
port: 3000
services:
postgres: true
redis: true
containers:
- name: worker
dockerfile: ./Dockerfile
command: node dist/worker.js
cpu: 0.25
memory: 256
hooks:
on_deploy: npm run db:migrate
Output format
Write nexos.yaml to the repo root. If you created a Nexos-specific Dockerfile, write it as Dockerfile.nexos at the repo root. Do not modify the existing Dockerfile. Do not write any other files. Do not add code comments to nexos.yaml unless they explain a non-obvious decision (e.g. why a specific Postgres version is pinned). After writing, tell the user to commit and push — Nexos applies the file on the next push.