Skip to main content
All pages in this manual

Running your project

sixpanel.json — your website's setup in a config file in its repository

What this page is for: keeping a website's whole setup — how it is built and started, the variables it needs, its cron jobs, its PHP settings — in one file, sixpanel.json, at the top of its own repository (or its ZIP). SixPanel reads that file at every deploy and sets the website up the way it says, the way vercel.json works on Vercel. Change the file, push, and the next deploy applies the change; nothing has to be clicked twice on a new server.

You need

  • A website of one of these kinds: static website, Node.js app, PHP website, WordPress, Python app or Java app (Websites and apps). A Docker app, a reverse proxy and a Create with AI website never deploy files of their own, so they have no such file; a 6amMart project's setup stays on its own pages (a sixpanel.json in a shop's code is reported in the update log and nothing of it is applied).
  • Nothing to install. A website with no sixpanel.json deploys exactly as it always did.

The file

sixpanel.json sits at the top of the website's files: the root of its git repository, or the top of the ZIP or folder you upload. It is plain JSON — no comments — of at most 64 KB, and it must be a plain file (a link or a folder at that name is refused). The smallest file is:

{ "version": 1, "start": "node server.js" }

version is required and is 1. Every other key is optional: say one thing, or everything. A $schema line is allowed, so your editor can check the file as you type it (see The JSON Schema).

When it is read: at every deploy — a git push, Deploy now, an upload — once the new version's files have arrived and before anything of them runs. In this order:

  1. Checked. A file that is not valid JSON, says another version, or has a value of the wrong kind (a command of two lines, a folder outside the project, a cron schedule that is none) stops the deploy before anything changes, and the live version keeps serving. The deploy log names the key that is wrong. A key SixPanel does not know is only a warning (a newer file on an older panel), never a stop.
  2. Built with it. The new version is built and started with what the file says.
  3. Applied when it is live — and only then. The website's settings take the file's values, the environment defaults are added, and the cron jobs are made exactly the file's list. A deploy that fails changes nothing the file asked for.

Every deploy log has one sixpanel.json section: each change as a line ("start command: node server.js → node .next/standalone/server.js"), each cron job added, changed or removed, each key it ignored and why, and each variable it needs that the website does not have yet.

What it can set

KeyForWhat it sets
kindallThe kind of website the file is written for. A website of another kind is never converted: the deploy stops and says both kinds.
runtime.nodeNode.js, a static website with a buildThe Node.js line, like "24". It must be installed on this server (Software → Node.js); if it is not, the deploy stops and says so.
runtime.phpPHP, WordPressThe PHP version, like "8.4" — composer and the deploy commands already run on it, and the website moves to it when the new version goes live. It must be installed (Software → PHP).
runtime.pythonPythonThe Python the app expects, like "3.13". A Python app runs on the server's own Python, so another one stops the deploy and names both.
runtime.javaJavaThe Java version, like "21" — that one when it is installed, otherwise the oldest installed one that is newer, or the server's own package is installed by the build.
build.installNode.js, PythonThe install command.
build.commandstatic, Node.js, JavaThe build command.
build.outputstatic, Node.js, JavaThe folder that is served (static) or the build's output folder (Node.js); for a Java app, the JAR its build makes, like target/app.jar.
startNode.js, PythonThe start command (a Python app's own start command replaces gunicorn/uvicorn).
rootPHP, static without a buildThe folder the web server serves, like public.
env.requiredNode.js, Python, Java, PHPNames of the variables the app needs. A missing one does not stop the deploy — the log and the website's Overview say which are missing. You type their values on the website's Environment page.
env.defaultsNode.js, Python, Java, PHPValues that are not secret, set only where the website has no value yet (a value you set on the page always wins). A PHP website gets them as lines in its .env.
cronevery kind but staticThe website's cron jobs from the file — see Cron jobs from the file.
hooks.afterBuildkinds with a build, and PHPA command run as the website, in the new version, after its build (a PHP website: after composer, before its deploy commands) and before it goes live. If it fails, the deploy stops and visitors keep the version they had.
php.settingsPHP, WordPressOnly what the website's PHP page lets you change, within its limits: memory_limit, upload_max_filesize, post_max_size, max_execution_time, max_input_time, max_input_vars, display_errors, short_open_tag, timezone, allow_url_fopen, session_lifetime.
php.extensionsPHP, WordPressExtensions to switch on for this website, like ["intl", "imagick"]. They are only ever turned on — one you switched on yourself stays on.
php.mailPHP, WordPressfalse turns PHP mail() off for the website. true turns it on only after the server's owner says yes, with the risk the PHP page's switch states (see asked once).

A variable whose name reads like a secret — ending in _SECRET, _KEY or _TOKEN, or holding PASSWORD or _PRIVATE, or DATABASE_URL — is never taken from env.defaults: its value would sit in your repository for anyone who can read it. Name it in env.required and type its value on the Environment page.

The rest of the website: three kinds of key

The file can set everything a website's pages set. What keeps that safe is that each key is in one of three kinds, by what it can cost or expose:

  • Applied at every deploy — what only changes how this website builds and runs in its own sandbox: the table above and the first table below.
  • Asked once — a yes first — what creates or exposes something: a domain, a database, the website's own Redis, a cache of its own, Apache, nginx directives, PHP mail() on. Nothing of it happens before a yes.
  • Never, whoever says yes — the server's firewall, SSH and FTP, Cloudflare, the panel's own settings, another website, a secret's value, anything past your account's limits.

Every value is checked the way the page that sets it checks it, with that page's limits, and saved by that page's own save. A value the page would refuse stops the deploy with the page's own sentence; so does a value past your account's limits (its memory, its PHP workers, the memory one PHP request may use) — a file never gets a website more than its package holds, and never quietly less than it says.

Applied at every deploy

KeyForWhat it sets
node.memoryMbNode.jsThe app's memory limit in MB (128 to 65536) — Settings → Advanced → Memory limit. Its heap is three quarters of it.
node.pageCacheNode.jstrue turns on the page cache: a page the app marks public is kept for 10 seconds for visitors who carry no cookie. The same as Settings → Advanced → Page cache.
node.processesNode.jsHow many copies of the app serve its visitors: 1 (the default), up to 16, or "auto" (one a processor core, at most 8). The same as Settings → Advanced → Processes. Keep 1 for an app that holds sessions, websockets or timers in its own memory.
python.memoryMbPythonThe app's memory limit in MB.
python.workersPythongunicorn or uvicorn workers, 1 to 32.
python.startTimeoutSecPythonHow long a new version may take to answer the first time, 15 to 900 seconds.
java.memoryMbJavaThe app's memory limit in MB; the heap (-Xmx) follows it.
java.jvmOptionsJavaJVM options on one line, without -Xmx.
java.argsJavaThe app's arguments, on one line.
java.startTimeoutSecJavaAs for Python.
health.pathNode.js, Python, JavaThe path a new version must answer before visitors are moved to it, like /healthz.
releases.keepstatic, Node.js, Python, JavaHow many versions stay on disk, the live one among them — the others are ready for a rollback (3 unless set, 1 to 10). Settings → Advanced.
web.maxBodyMbstatic, Node.js, Python, JavaThe largest upload nginx takes, in MB (1 to 4096). Settings → Advanced. A PHP website takes what its PHP takes: set php.settings.upload_max_filesize.
web.spastaticA single-page app: a path with no file shows index.html.
web.notFoundstaticThe page shown for a path with no file, like 404.html (while spa is off).
web.headersevery kindResponse headers, as Web Rules takes them: [{ "name": "X-Robots-Tag", "value": "noindex" }], or "remove": true (at most 10). The file's list is the website's header list. Never Set-Cookie, and never a security header SixPanel sends (Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) — the deploy stops; Web Rules can change those on its page.
web.redirectsevery kindRedirects: [{ "from": "/old", "to": "/new", "status": 301 }] — status 301, 302, 307 or 308 (301 unless set), match exact, prefix or regex, and keepQuery. They replace the website's redirects (at most 25); a Rewrite you typed on Web Rules stays.
php.poolPHP, WordPressThe PHP workers, as the PHP page sets them: pm (ondemand, dynamic, static), max_children (1 to 200), idle_timeout (5 to 600), max_requests (50 to 100000).
php.sessionsPHPfiles or redis, as the PHP page's Sessions switch. Redis needs the website's own Redis (one the file asks for counts once it has its yes). A switch signs every visitor out once.
cache.pageWordPressWordPress's page cache on or off. On turns Varnish off for the website: the two are never on together.
cache.brotlievery kindBrotli is the whole server's (Software → Brotli): true holds when the server has it on, and is otherwise listed as the owner's; false is ignored — one website does not turn it off.
cache.varnish.purgeOnDeploywebsites behind Varnishfalse keeps Varnish's copy at a deploy (it then ages out by its own time) — Caching → Varnish → Clear it at every deploy. Purge and a rollback always clear it.

Domains, databases, Redis and caches in sixpanel.json: asked once

KeyForWhat it makes
domainsevery kind[{ "name": "shop.example.com", "primary": true, "www": true }, { "name": "old.example.com", "redirectTo": "shop.example.com" }] — each name added as Domain & SSL adds it, with HTTPS the panel's own way (Cloudflare when its token is there, otherwise the usual certificate). A name that does not reach this server yet is connected by itself once it does. redirectTo is the primary domain.
databasesNode.js, Python, Java, PHP[{ "engine": "postgres", "name": "app", "env": "DATABASE_URL" }] — engine is mariadb, postgres, mongodb or mssql (installed on the server), name 1 to 16 lower-case letters and digits. The panel makes the database and its user and generates the password; the address, password included, goes into the variable you name (a PHP app: into its .env). The website's first database is called site_<website>, the others site_<website>_<name>. The password is never in the file, the deploy log, the panel's answers or Tia's.
redisevery kind but statictrue, or { "env": "REDIS_URL" } — the website's own Redis, its address in the variable named (REDIS_URL unless set).
supabaseNode.js, Python, Java, PHP{ "use": "my-supabase" } — connects the website to the Supabase app called my-supabase on this server, as Environment → Supabase → Use a Supabase on this server does: its URL, publishable key, service_role key and database address go into NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SERVICE_ROLE_KEY and SUPABASE_DB_URL, or the names you give in "env": { "url": …, "anon": …, "service": …, "db": … } ("" leaves one out). No key is ever in the file. The server's owner says yes. The file never makes a Supabase — make it first on Projects → Create → Docker app → Supabase — and never connects Supabase Cloud, whose keys are typed on the Environment page.
cache.memcachedevery kind but statictrue, or { "memoryMb": 64 } — a Memcached of the website's own. The server's owner says yes.
cache.varnishwebsites Varnish can serve{ "enabled": true, "ttl": 120 } — Varnish in front of the website, and how long it keeps a page that says nothing. The server's owner says yes.
web.serverPHP"apache" or "nginx" — what serves its PHP (Apache reads .htaccess; Apache must be installed). The server's owner says yes.
web.nginxevery kindThe Vhost page's own directives, a line each: { "server": ["gzip_comp_level 6;"], "locations": { "root": [], "php": [], "app": [] } } — through that page's check, nginx's test and its put-back. It needs the website's HTTPS first. The server's owner says yes.
php.mailPHP, WordPresstrue — PHP mail() on. The server's owner says yes, after reading the risk its switch states.

How you approve what sixpanel.json asks: Update, or the bell

  • Update on a git website's Deploys page lists what the commit's file asks, each line ticked. Pressing Update now with a line ticked is your yes, kept from that moment — a deploy that then fails keeps it. The server's owner says yes to every line. An account says yes, on its own website, to a domain, a database and its Redis — and a reseller on its users' websites; a Memcached, Varnish, Apache, the Vhost directives and PHP mail() are the server owner's alone: shown, and waiting for the owner.
  • The Vhost directives are listed line by line, exactly as nginx will read them, and neither they nor PHP mail() are ticked for you: read them, then tick.
  • The bell at the top of the panel (the server's owner): what a git push, Tia, an MCP client or an upload deployed, and every line no press ticked, waits there — one card per item. A yes there makes it at once. Tia and an MCP client can never say yes for you, and neither can a temporary login.

Once you said yes, the same item applies at later deploys without asking again. A changed item (another variable name, another memory size) asks again — a new name for a database's variable then points at the SAME database, never a second, empty one. Something you undid by hand on its page since (PHP mail() turned off, a domain or a database removed, Apache switched off) is asked again: the next deploy never puts it back on the old yes. Taking an item out of the file never removes what it made: the website's Overview card says it is no longer in sixpanel.json, with a link to the page that removes it.

A deploy that does not go live gives back the variables and the .env lines it wrote for the new version, so the version still live keeps its own database — and if the panel itself stopped in the middle of that deploy, its next start puts those .env lines back (their old values are kept until then in a file only the server's root can read, never shown anywhere). What was made for it stays (a database is never dropped) but is not the website's yet: the website's Overview card says made, waiting for its release, so does the database on the Database page (which says when your app is given its variables, instead of that it has them), and none of its variables (DATABASE_URL, DB_*, REDIS_URL…) reach your app — the version still live included, when it restarts — until a version whose file asks for it goes live. That version's build and deploy commands already see it. A Node.js app's first version waits for its HTTPS address ("read, not built yet"): the domain its file names is asked for at once — in the bell, or added right away when you ticked it — and connecting it builds the version.

An account's, a temporary login's, Tia's deploy of sixpanel.json

A file is never more than whoever deploys it could do on the website's pages. Deployed by an account, its PHP settings, PHP workers and PHP extensions are not applied (they are the server owner's on the PHP page). Deployed by a temporary login, no environment variable is added and neither the PHP settings and workers nor where PHP keeps its sessions change (those pages are the owner's or the account's). Neither changes whether a deploy clears Varnish (Caching, the owner's). A git push applies what the website's account could set (the owner's own website: everything). Each such key is listed as ignored, with why and a link to its page; the rest of the file applies, and the owner's own deploy applies all of it. The same holds on the website's Settings: a temporary login or an account that sends the PHP settings, the PHP workers or the Varnish clearing there is refused, with the sentence those pages give.

Deployed by Tia or an MCP client, a file applies only what Tia's own tools may change without asking you. What they would ask you first waits for your yes in the bell, one card per key showing exactly what a yes applies, and the rest of the deploy goes live without it:

Waits for your yesWhy
php.settingsthe PHP page's settings are yours
php.poolthe PHP workers, on the same page
php.sessionswhere PHP keeps sessions — every visitor signs in again; Tia never changes it on her own
java.jvmOptions, java.argspart of the command the app starts with
cron (a new or changed job)a scheduled task runs a command; a job the file only drops is removed
env.defaults that switch a Laravel app's debug pages on (APP_DEBUG on, APP_ENV not production)debug pages show passwords and keys to whoever triggers an error

A yes there applies it at once, through that page's own save (a Java app's JVM options and arguments when it starts next), and is kept: the next deploy by Tia with the same value applies it without asking, while a changed value asks again.

Everything else goes live with the deploy: runtime.node, runtime.php, runtime.java, build.output, root, env.defaults (unless they switch debug pages on), cron (a job the file only drops), php.extensions, php.mail, node.memoryMb, python.memoryMb, python.workers, python.startTimeoutSec, java.memoryMb, java.startTimeoutSec, health.path, releases.keep, web.maxBodyMb, web.spa, web.notFound, web.headers, web.redirects, cache.page and cache.varnish.purgeOnDeploy — and the build, install and start commands and afterBuild (build.install, build.command, start, hooks.afterBuild), which are never held: they are the repository's own code, run as the website by every deploy of it. Your own deploy applies the whole file, and the cards for what it applied go. Tia and an MCP client never say yes to anything a file asks.

What sixpanel.json never sets: the firewall, SSH, Cloudflare, another website

The server's firewall, SSH and FTP users, Cloudflare's settings, the panel's own settings, another website or project, a secret's value, and anything past your account's limits — more than your package holds, a name another website uses, software the server does not have. Such a key is read and listed as ignored, with why and a link to the page where it is set.

Who changed what: sixpanel.json on Activity

Every change a file made is on Activity: who deployed (the owner, an account, a temporary login, a git push with its commit, Tia or the MCP client by name — a rollback, Build and Deploy files by the person who pressed them), which commit or upload, and each key from what it was to what it is now (for a rollback, what the rollback changed). A variable shows by its name only, and a password in a start command or a program's arguments shows as ****. Every yes is there too.

A rollback runs the earlier version with what ITS file set — its memory, health check, largest upload and the rest — not the newer file's; a setting no file set stays as you have it. What waits for a yes is then what that version's file asks, and a version with no file asks nothing.

Cron jobs from the file

Each job has a name (lower-case letters, digits and dashes, once in the file), a schedule (the same five fields the Cron jobs page takes — minute, hour, day of the month, month, day of the week — or @hourly, @daily, @weekly, @monthly, @yearly) and exactly one of:

  • "command" — runs as the website's account in its live version, as a job you add by hand does;
  • "path" — asks an address of the website itself with a GET, like "/api/cron/daily".

"enabled": false keeps a job in the file but switched off.

A job asking an address sends Authorization: Bearer <CRON_SECRET> when the website has a CRON_SECRET variable, and User-Agent: SixPanel-Cron/1 — what Vercel Cron sends, so a route written for Vercel works unchanged. The answer must be 2xx; anything else (a 500, a redirect, no answer) is a failed run, shown on the Cron jobs page like a command that failed. A Node.js, Python or Java app is asked on the server itself, at the version that is live — whichever of its two slots is serving, and before it has a domain; while the app is stopped it is asked nothing (each run says so), until Start runs it again. A PHP or WordPress website is asked through this server's web server at its domain, so it needs its domain with HTTPS first (until then each run fails and says why).

The file's list is the list. At every deploy that goes live, the jobs the file made are made exactly what the file says now: a new one is added, a changed one is changed, one no longer in the file is removed. The Cron jobs page marks them From sixpanel.json. A job you added by hand is never touched, renamed or removed by the file — even one with the same name. If a later version has no sixpanel.json at all, the jobs the file made go, and what else it set stays as it is. Rolling back to an earlier version (the Deploys page) brings back the jobs of the file that version had — or none, when it had no file.

Coming from Vercel (vercel.json)

SixPanel does not read vercel.json, but its parts have a place here:

vercel.jsonsixpanel.json
"crons": [{ "path": "/api/cron/x", "schedule": "0 5 * * *" }]"cron": [{ "name": "x", "path": "/api/cron/x", "schedule": "0 5 * * *" }] — a name is added
"installCommand""build": { "install": … }
"buildCommand""build": { "command": … }
"outputDirectory""build": { "output": … }
environment variables in the dashboard"env": { "required": [ … ] }, and their values on the Environment page

Your cron routes that check CRON_SECRET keep working: give the website the same CRON_SECRET on its Environment page.

Who wins

For a key the file sets, the file wins at every deploy — over what SixPanel found in your files and over an earlier change on the page. On the website's Settings page such a field is marked Set by sixpanel.json: you can still change it there, and the next deploy puts the file's value back. Change the file instead. A key the file does not set stays yours, on the page.

What it never does

Whoever wrote the file — anyone who can push to the repository can — it only sets what the person who may deploy the website could set on its pages, with the same checks and the same limits (an account's limits included). It never:

  • holds a secret value (see the rule above);
  • makes a domain, a database, a Redis or a cache before a yes (see asked once);
  • changes the firewall, Cloudflare, SSH or FTP, the panel's own settings, or another website;
  • removes what an earlier file made;
  • turns the website into another kind.

A file that carries such a key (firewall, cloudflare, ssh, secrets, sites …) is still read; the deploy log and the card list the key, why it was ignored and where it is set. A name written the wrong way (domain, ssl, database) is pointed at the key that does it.

Export a website as sixpanel.json

Any website of these kinds can be turned into its sixpanel.json, read from the website as it is now: open its Overview, and on the sixpanel.json card press Export sixpanel.json. The dialog shows the whole file; Download sixpanel.json saves it, Copy copies it.

No secret value is in the file — no password, key or token. Each secret is named instead:

  • one an outside service gave you (a Stripe key, a mail password) is listed in env.required — you type it where the file is deployed;
  • one the app makes up itself (APP_KEY, SESSION_SECRET, CRON_SECRET …) is listed in env.generate — a new value is made where the file is deployed, never this one;
  • the variables SixPanel gives the app (its database's address, its Redis's, its port) are not in the file at all: each server gives its own.

A cron job, a header or a Vhost line that holds a password is left out, and the dialog lists it under Not in the file with the page where you set it again. So are SSH and FTP users, extra database users, the data in the databases (and a WordPress website's posts and uploads — Backups or Move a website carry those), PHP's disabled functions and free php.ini lines, and the repository's access token or deploy key. What SixPanel works out from your files by itself (the build and start commands it detects, the document root) is written only when you changed it on the page — the new server works it out the same way.

Deploying the file back onto the website it came from changes nothing: its databases are its own (never a second, empty one), and a cron job you made by hand is not added twice.

Make a new website from a sixpanel.json

On Projects → Create, press From a sixpanel.json on the first screen. Paste the file, choose it, or read it from a repository's branch (a private repository needs its access token, which stays on this server). Nothing is made yet: you see what kind of website it is, where its files come from, each secret it names, and what it makes.

  • Secrets: type each one the file lists in env.required. One left empty does not stop anything — the website's Overview lists it, and you type it on its Environment page later. What env.generate names is made fresh at the first deploy.
  • What it makes — each thing the file asks for once (see asked once above) is a ticked line, and your press of Make the website is your yes, kept exactly as a ticked line on Update is. The Vhost directives and PHP mail() are shown and not ticked for you. An account sees what only the server's owner may say yes to without a tick: it waits in the owner's bell.

If the file names its repository ("source": { "git": "https://…", "branch": "main" }), the website is connected to it and its first deploy starts by itself — with push-to-deploy on when the file says "autoDeploy": true. Otherwise the file is kept with the website: the first files you upload on its Deploys page are set up by it, and every later deploy whose files carry no sixpanel.json reads it too. A sixpanel.json in the website's own files always wins.

A file that cannot make the website — written for another kind, asking for a Node.js or PHP this server does not have, past your account's package — makes nothing, and the dialog says why.

Moving a website's setup to another server is then: Export sixpanel.json here, From a sixpanel.json there, type the secrets it names. (Move a website, on the server's own pages, moves the files and the data too.)

Keys round 166 added

KeyForWhat it sets
hooks.deployPHP, Node.js, Python, JavaThe Settings page's deploy commands, one line each: ["php artisan migrate --force"] (20 at most).
keepPHP, Python, JavaFiles and folders kept between deploys, like ["storage/"] — kept from the deploy that first names them.
env.generateNode.js, Python, Java, PHPSecrets made fresh where the website has no value — an empty APP_KEY= too. The value never appears in a log or on a page but Environment.
web.rewritesevery kindWeb Rules Rewrites: [{ "from": "/shop", "to": "/store", "match": "prefix" }] — the address stays, another path answers.
web.blockPathsPHPRefused addresses, like ["/install"] — a 404 before PHP sees them.
web.frontControllerPHPtrue sends an address with no file to index.php; false answers 404.
node.modeNode.js"server" or "static".
python.serverPython"gunicorn", "uvicorn" or "custom".
python.appModulePythonLike "mysite.wsgi:application".
python.settingsModulePythonDjango's settings module, like "mysite.settings".
python.collectstaticPythonfalse skips collectstatic.
java.mainClassJavaLike "com.example.App".
wordpress.securityWordPress{ "xmlrpc": false, "userEnum": false, "loginLimit": true, "fileEdit": false } — the Settings page's switches.
name—What a website made from the file is called. Read when it is made, never at a deploy.
source—{ "git": "https://github.com/owner/repo.git", "branch": "main", "autoDeploy": true } — where a website made from the file takes its files. Never a token. Read when it is made, never at a deploy.

A redirect or a Rewrite can say when it applies, as Web Rules' condition does — "when": { "type": "device", "value": "mobile" } (type uri, query, host, country or device; op eq, ne, re or nre) — and "enabled": false keeps a rule switched off. A header's "always": false sends it on a good answer only. A deploy by Tia or an MCP client applies all of these.

Check sixpanel.json before you deploy

The website's Overview has a sixpanel.json card: which version's file is in force, what it set, what it asked for (made with a yes, waiting for one, or already there), what it ignored and which variables it needs are still missing. Check sixpanel.json reads the file the next deploy would read — from the newest commit of the repository's branch for a git website, or from the last upload — and shows what it would change, without deploying anything.

Examples, one per kind

Static website

A site built with Vite into dist/:

{
  "version": 1,
  "kind": "static",
  "runtime": { "node": "24" },
  "build": { "command": "npm run build", "output": "dist" }
}

Node.js app

An Express app with a cleanup job every 15 minutes:

{
  "version": 1,
  "kind": "node",
  "build": { "install": "npm ci", "command": "npm run build" },
  "start": "node dist/server.js",
  "env": { "required": ["SESSION_SECRET"], "defaults": { "NODE_ENV": "production", "LOG_LEVEL": "info" } },
  "cron": [{ "name": "cleanup", "schedule": "*/15 * * * *", "command": "node dist/cleanup.js" }],
  "hooks": { "afterBuild": "npx prisma migrate deploy" }
}

PHP website

A Laravel app: served from public/, PHP 8.4 with intl, the scheduler every minute:

{
  "version": 1,
  "kind": "php",
  "runtime": { "php": "8.4" },
  "root": "public",
  "env": { "required": ["APP_KEY"], "defaults": { "APP_ENV": "production", "APP_DEBUG": "false" } },
  "cron": [{ "name": "scheduler", "schedule": "* * * * *", "command": "php artisan schedule:run" }],
  "hooks": { "afterBuild": "php artisan migrate --force" },
  "php": { "settings": { "memory_limit": "512M", "upload_max_filesize": "64M", "post_max_size": "64M" }, "extensions": ["intl"], "mail": false }
}

WordPress

A theme or plugin repository that also sets the site's PHP:

{
  "version": 1,
  "kind": "wordpress",
  "runtime": { "php": "8.3" },
  "php": { "settings": { "memory_limit": "256M", "max_execution_time": 120 }, "extensions": ["imagick"] }
}

Python app

A Django app started by gunicorn with three workers, and a nightly job:

{
  "version": 1,
  "kind": "python",
  "build": { "install": "pip install -r requirements.txt" },
  "start": "gunicorn mysite.wsgi:application --workers 3",
  "env": { "required": ["DJANGO_SECRET_KEY"], "defaults": { "DJANGO_SETTINGS_MODULE": "mysite.settings" } },
  "cron": [{ "name": "clearsessions", "schedule": "0 3 * * *", "command": "python manage.py clearsessions" }]
}

Java app

A Spring Boot app built with Maven:

{
  "version": 1,
  "kind": "java",
  "runtime": { "java": "21" },
  "build": { "command": "./mvnw -q -DskipTests package", "output": "target/app.jar" },
  "env": { "defaults": { "SPRING_PROFILES_ACTIVE": "prod" } }
}

The whole website in one file (Node.js)

{
  "version": 1,
  "kind": "node",
  "start": "node .next/standalone/server.js",
  "node": { "memoryMb": 1024 },
  "health": { "path": "/api/health" },
  "releases": { "keep": 5 },
  "web": {
    "maxBodyMb": 50,
    "headers": [{ "name": "X-Robots-Tag", "value": "noindex" }],
    "redirects": [{ "from": "/blog", "to": "https://blog.example.com", "status": 301, "match": "prefix" }]
  },
  "domains": [{ "name": "shop.example.com", "primary": true, "www": true }],
  "databases": [{ "engine": "postgres", "name": "app", "env": "DATABASE_URL" }],
  "redis": { "env": "REDIS_URL" }
}

The first Update lists the domain, the database and the Redis, each ticked; pressing Update now makes the database before the app's migrations run, and the domain with HTTPS once the new version is live. Every later deploy asks nothing about them.

A worked example: allsweb.com (Next.js)

allsweb.com is a Next.js app (output: 'standalone') with three Vercel-style cron routes that check CRON_SECRET. Its sixpanel.json (the real one names every variable the site reads; five are shown):

{
  "$schema": "https://installer.allsweb.net/sixpanel/sixpanel.schema.json",
  "version": 1,
  "kind": "node",
  "runtime": { "node": "24" },
  "health": { "path": "/api/health" },
  "env": {
    "required": ["NEXT_PUBLIC_APP_URL", "NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY", "SUPABASE_SERVICE_ROLE_KEY", "CRON_SECRET"],
    "defaults": { "NODE_ENV": "production" }
  },
  "cron": [
    { "name": "infra-daily", "schedule": "30 0 * * *", "path": "/api/cron/infra-daily" },
    { "name": "infra", "schedule": "0 12 * * *", "path": "/api/cron/infra" },
    { "name": "agent", "schedule": "15 6 * * *", "path": "/api/cron/agent" }
  ]
}

There is no build and no start, on purpose: SixPanel reads both from the project itself. Its packageManager ([email protected]) runs through npx at exactly that version, and output: 'standalone' starts as .next/standalone/server.js with .next/static and public copied beside it. Writing them in the file is allowed — but a plain pnpm there needs corepack, which Node.js 25 and later no longer ship.

On a server that runs its own Supabase (a Docker app called supabase here), one more key replaces typing the three Supabase values by hand — the panel reads them from that app, and follows it when its keys or address change:

  "supabase": { "use": "supabase" }

At its first deploy the log's sixpanel.json section says the health path, the Node.js line and the three cron jobs it adds, and which variables are missing until they are typed on the Environment page; NODE_ENV is added because the website has no value for it. Node.js 24 must be installed on the server first (Software → Node.js) — the deploy says so if it is not. A new version goes live only once /api/health answers. A second deploy of the same file changes nothing; changing one job's schedule in the file changes exactly that job at the next deploy.

The JSON Schema

Editors such as VS Code check the file as you type when its first lines name the schema:

"$schema": "https://installer.allsweb.net/sixpanel/sixpanel.schema.json"

The panel carries the same schema: the sixpanel.json card on a website's Overview links to it. The schema describes version 1; SixPanel itself is what checks the file at a deploy.

How to check it worked

  • The deploy log has a sixpanel.json section, and it says what you expected.
  • The website's Overview card names the version whose file is in force.
  • Cron jobs lists the file's jobs marked From sixpanel.json; Run now on a job that asks an address shows the answer's status in its log (HTTP 200).

If it went wrong

  • "sixpanel.json: … the deploy stopped before anything changed" — the file says something wrong; the message names the key. Fix the file and deploy again. The live version was never touched.
  • "is written for a Node.js app … and this is a PHP website" — kind names another kind. A website is never converted: make a website of that kind, or correct kind.
  • "asks for Node.js 24, which is not installed" — install it on Software → Node.js (the server's owner), or remove runtime.node from the file.
  • A cron job that asks an address fails with "not run: this website has no domain that answers over HTTPS yet" — a PHP or WordPress website is asked at its domain: add it on Domain & SSL.
  • "HTTP 401" in a job's log — the website has no CRON_SECRET, or not the one the route expects: set it on the Environment page.
  • A variable is still missing — the card on the Overview lists it; type its value on the Environment page (the file never holds a secret's value).
  • "waits for a yes" — a domain, a database or another item the file asks for has no yes yet: tick it the next time you press Update, or answer it in the bell (the server's owner).
  • "…this website's account has 768 MB for all its websites" (or its PHP workers, or its PHP memory) — the file asks past the account's package; lower the value in the file, or ask the server's owner for a larger package.

Something unclear?Ask Tia