Frameworks — zeltro new

zeltro new <framework> <name> scaffolds a greenfield project you write. Both arguments are required positionals.

zeltro new laravel my-shop
zeltro new flask my-api --database sqlite
zeltro new express my-service --database postgres --version latest

For ready-made third-party apps you run rather than write, see App library instead.


Supported frameworks

Framework Runtime image Default database
laravel PHP 8.3 MySQL
kavera PHP 8.3 MySQL
octobercms PHP 8.3 MySQL
wordpress PHP 8.3 MySQL
php PHP 8.3 MySQL
fastapi Python 3 PostgreSQL
flask Python 3 PostgreSQL
django Python 3 PostgreSQL
python Python 3 PostgreSQL
express Node 22 MySQL
nestjs Node 22 MySQL
fastify Node 22 MySQL
node Node 22 MySQL
nextjs Node 22 SQLite
nuxt Node 22 SQLite
sveltekit Node 22 SQLite
astro Node 22 SQLite
hono Node 22 SQLite
react Node 22 SQLite
vue Node 22 SQLite

Front-end frameworks

nextjs, nuxt, sveltekit, astro, react and vue are scaffolded as running dev servers with hot reload, proxied through nginx on port 80. Edit a file and the page updates — no build step, no port to remember.

They default to SQLite, unlike every other framework here, because a front-end project should not start a database server it never queries. Ask for one explicitly when you need it:

zeltro new nextjs my-app --database postgres

react and vue are plain single-page apps on Vite with no server rendering. hono is an API framework rather than a UI one, and is grouped with them only because it shares the same Node base image.

Hot reload is wired for you. The dev server’s own port is never published — the browser reaches the app on port 80 through nginx — so each project’s Vite config pins the HMR socket to port 80. Change that and hot reload stops connecting while the page still loads, which is a confusing failure.

Kavera

Kavera is a Laravel-native website framework: flat-file Blade pages plus service-driven dynamic content (Blogger posts, Eventbrite events, Flickr galleries, form webhooks) cached through Redis. Forms ship with email, webhooks, spam controls and reCAPTCHA; pages carry SEO titles and optional JSON-LD.

It exists because an agent editing page files beats an agent driving a CMS admin UI. Reach for it over plain Laravel for marketing sites, brochure sites, portfolios and galleries — and for plain Laravel when you need a real application with custom models and business logic.

zeltro new kavera my-site

Pages live in resources/views/content. After adding or removing one, refresh the registry so routes resolve:

zeltro art app:update-content-list

Options

Option Description Values
--database <type> Database engine auto (default), mysql, postgres, mongodb, sqlite
--db-name <name> Database name Default: project name, dashes → underscores
--version <ver> Framework version Laravel / WordPress: latest or a version tag
--image <ref> Override the Docker image Default: the framework’s base image
--no-migration Skip migrations Migrations run by default
--one-off Skip the AI hand-off after creation  
--github Create a GitHub repo in your account Requires gh auth
--github-org <org> Create the repo in an organization  
--public / --private Repo visibility Default private
--no-storage-symlink Skip public/storage symlink Laravel only

Databases

SQLite

zeltro new flask notes --database sqlite
zeltro new django blog --database sqlite
zeltro new laravel shop --database sqlite

SQLite needs no shared service — it’s a single file. Zeltro creates it, points the project’s .env at it, and runs migrations normally.

The file always lives inside the project directory:

Framework Path
Django db.sqlite3
Laravel database/database.sqlite
Everything else database.sqlite

That location is deliberate. The project directory is the only path bind-mounted into the container, so a database anywhere else would be destroyed every time the container is recreated on zeltro up. It is also gitignored by default.

Good for prototypes, single-user tools, and test fixtures. For anything concurrent, use Postgres or MySQL.

Shared server databases

mysql, postgres and mongodb connect to the shared service containers. Zeltro creates the database and writes the connection settings into the project’s .env — you never configure credentials by hand. See Architecture → Shared services for hostnames and credentials.


Working inside a project

Run these from the project directory. They execute inside the container, with the correct runtime.

PHP

zeltro composer install
zeltro art migrate
zeltro wp plugin list --status=active
zeltro php script.php
zeltro tinker

Python

zeltro python -c "import sys; print(sys.version)"
zeltro pip install httpx
zeltro django manage migrate
zeltro django manage createsuperuser
zeltro shell

Python containers provide python3, not python.

Node

zeltro npm install
zeltro npx tsc --init
zeltro node script.js
zeltro shell

Any framework

zeltro exec <cmd>              # run a command, no TTY — good for scripts and CI
zeltro exec-root <cmd>         # as root
zeltro bash                    # interactive shell
zeltro shell                   # framework-aware REPL (tinker / django shell / node / python3)
zeltro supervisor restart all  # restart in-container processes
zeltro supervisor-status

Use zeltro supervisor, never zeltro exec supervisorctl — the latter runs as the developer user and is denied on the supervisor socket.

Laravel extras

zeltro db-refresh      # fresh migration + seed
zeltro cache-refresh   # clear all caches
zeltro phpcs app/      # static analysis
zeltro phpcbf app/     # auto-fix
zeltro phpmd app/File.php
zeltro php -l app/File.php

Adopting an existing project

zeltro setup my-project                          # a folder already in ~/zeltro-projects/
zeltro setup my-project --framework django       # force detection
zeltro setup my-project --overwrite-env          # repoint an existing .env at shared services
zeltro setup my-project --no-startup             # register without starting, to review the compose

Framework detection reads the project’s files — artisan, manage.py, main.py, app.py, package.json, wp-config.php. Flask and FastAPI are distinguished by which one the file actually imports, not by filename.