How do I configure a project with hodifly.json?
Hodifly can take its settings from a hodifly.json file at the root of your repository, instead of from the form in cPanel. The file is optional: without it, Hodifly detects what your project is and the form holds the settings. With it, the repository configures itself, the configuration is versioned with your code, and it rolls back with it. Deploying the same repository twice gives you two identical projects.
{
"mode": "php",
"runtime": "php:8.3",
"docroot": "public",
"build": "npm run build",
"persist": ["config/app.yaml", "storage/uploads/"],
"env": { "required": ["DB_HOST", "DB_USER"] },
"keepReleases": 5
}
Put it at the root of the repository, or at the root of the package folder for a project that lives in a subfolder of a monorepo. You do not have to write it by hand: in cPanel, open your project and click hodifly.json to generate one from the settings you already have.
Every setting
Setting | What it does |
|---|---|
| How the project is served: |
| The toolchain that builds it, and for PHP the version that serves it: |
| Replaces the default dependency install ( |
| The build command. An empty string means no build: the repository is published as it is. |
| Static sites only: the folder your build writes into ( |
| PHP only: the front-controller folder inside the release, |
| Node and Python apps: the file Passenger runs to start the app, relative to the project root: |
| The paths that must survive a deploy. See below. |
| The names of the environment variables your project cannot run without. |
| How many releases to keep for rollback, from 1 to 50. Five by default. |
|
|
| Send every visitor arriving over plain HTTP to the |
|
|
|
|
| Rebuild on a timer as well as on push, in minutes: |
| Static sites only: |
Node and Python: the entry file
Passenger does not run npm start: it runs one file, and startup is how you say which one. It takes a path, not just a name, which is what a compiled application needs:
{
"mode": "node",
"runtime": "node:22",
"build": "npm run build",
"startup": "dist/main.js"
}
Three things worth knowing:
- The app is still started from the project root, whatever the depth of that path. Relative paths in your code, and the folders you listed in
persist(uploads, assets), keep working. - The file has to be CommonJS. Passenger loads it with
require(), so an entry that is an ES module (a.mjsfile, or a project with"type": "module") needs a small bridge file at the root, andstartuppoints at the bridge:import('./dist/server/server.mjs').catch(e => { console.error(e); process.exit(1); });in a file namedserver.cjs. Next.js, Nuxt, Astro, SvelteKit and Remix need none of this, Hodifly writes it for them. - Your server must listen. An Angular SSR build only starts listening when its
server.mjsis the main module, which it is not under Passenger: remove that condition solisten()always runs, onprocess.env.PORT.
You can also set it in cPanel, in the project's Startup file field. The file wins over the field.
A Ruby application has no such setting: Rack always starts from config.ru.
What it deliberately cannot set
- The package folder and the branch. They are how Hodifly finds this file in the first place.
- The domain and the folder on your account. The same repository on two domains is two projects: the address belongs to the hosting account, not to the code.
- Anything secret: environment variable values, the deploy-hook secret, the build-hook URL, your GitLab token. A repository is the one place those must never end up.
env.requirednames them, cPanel holds them.
persist: what survives a deploy
Every deploy creates a new release, and old releases are deleted once keepReleases newer ones exist. So anything your app writes inside its own folder is on a countdown, and anything your repository does not ship is missing from every release. persist fixes both. Each listed path is moved once into a folder that sits outside every release, and linked back into each new one, so it survives deploys and rollbacks.
- A trailing
/means a directory, no slash means a file. It is the only way Hodifly can know what to create when the path exists nowhere yet. - The first deploy decides the starting content. If your repository ships the path, that copy is kept. If it does not, the path is created empty and the deploy log prints exactly where, so you can fill it over FTP. Nothing is ever copied from a
.examplefile: what a template should become is your decision. - After that, the persisted copy wins. A version that later appears in the repository is ignored.
- Laravel's
storage/, Symfony'svar/logandvar/sessions, and any SQLite database named in your configuration are already handled. Do not list them. - You cannot persist the folder your site is served from: it would freeze the site on one release and no later deploy could replace the code.
Rolling back restores the code, never the data: your uploads, your database and your configuration stay as they are today. See "Can I roll back after a bad deployment?".
HTTPS: your site redirects on its own
Hodifly sends every visitor who arrives over plain HTTP to the https address of the same page, with a permanent redirect. Static sites, PHP applications and Node, Python or Ruby applications all get it, and you do not have to ask for it.
There is one condition, and it is the whole reason forceHttps exists: the redirect only appears once a real certificate actually covers your domain. The first deploy of a new site usually lands before the certificate has been issued, and sending visitors to an https their browser refuses would take the site down until it arrives. So the first deploy is served in clear and says so in its log, the certificate is issued, and the next deploy turns the redirect on by itself.
"forceHttps": falseturns the redirect off, for a site that has a reason to answer in clear."forceHttps": trueredirects without waiting for the certificate. Use it only when you know the certificate is already there: until it is, the site is unreachable.
The addresses your certificate authority checks under /.well-known/ are never redirected, so issuing and renewing a certificate keep working over HTTP.
Which settings apply when
Hodifly re-reads the file from your code on every deploy for everything that describes the build: build, install, output, docroot, startup, persist, env.required, keepReleases, noindex and forceHttps. Edit, push, done.
startup is in that list on purpose: the build is what decides which file starts your application, so moving your entry point should never mean going back to cPanel to make anything run. The value that was used is saved back onto the project, so the Startup file field shows what is actually running.
The rest (mode, runtime, autodeploy, previews, rebuildEvery, snippets) is applied when the project is created, or when you change its package folder. Changing one of those in the file later means changing it in the form too.
Where hodifly.json and a "vercel.json" say the same thing, hodifly.json wins, and the deploy log says so.
Check a file before you push
Paste it into the validator at hodifly.app/en/validate. It tells you whether the file is valid and, more usefully, exactly what it will do on the next deploy.
Good to know
- A file that is not valid JSON never breaks a deploy: it is ignored, with a warning in the deploy log, and the deploy continues with the settings from the form.
- Limits: 256 KB for the file, 500 settings, 4 KB per value.
env.requiredis a nice safety net. If a variable is missing, the deploy stops in a few seconds and names it, instead of your site answering with a blank page.
Updated on: 03/09/2026
Thank you!