Articles on: 🌍 Web Hosting & PaaS
This article is also available in:

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

mode

How the project is served: static, node, python, php or ruby. Overrides detection.

runtime

The toolchain that builds it, and for PHP the version that serves it: node:22, php:8.3, python:3.12, ruby:3.3, or none for no toolchain at all.

install

Replaces the default dependency install (npm ci, composer install).

build

The build command. An empty string means no build: the repository is published as it is.

output

Static sites only: the folder your build writes into (dist, _site).

docroot

PHP only: the front-controller folder inside the release, public for Laravel and Symfony. "." serves the repository root, for an app that has no such folder.

startup

Node and Python apps: the file Passenger runs to start the app, relative to the project root: server.js, dist/main.js for a NestJS build, passenger_wsgi.py. See below.

persist

The paths that must survive a deploy. See below.

env.required

The names of the environment variables your project cannot run without.

keepReleases

How many releases to keep for rollback, from 1 to 50. Five by default.

noindex

true asks search engines to skip the site, on every response. For a staging site.

forceHttps

Send every visitor arriving over plain HTTP to the https address. Hodifly already does this on its own once your domain has a certificate, so this setting is normally here to say false. See below.

autodeploy

false stops pushes from deploying; you deploy by hand from cPanel.

previews

true builds a preview URL for each pull request.

rebuildEvery

Rebuild on a timer as well as on push, in minutes: 0, 60, 1440 or 10080.

snippets

Static sites only: {"head": "...", "body": "..."} HTML injected into every built page.


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 .mjs file, or a project with "type": "module") needs a small bridge file at the root, and startup points at the bridge: import('./dist/server/server.mjs').catch(e => { console.error(e); process.exit(1); }); in a file named server.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.mjs is the main module, which it is not under Passenger: remove that condition so listen() always runs, on process.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.required names 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 .example file: 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's var/log and var/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": false turns the redirect off, for a site that has a reason to answer in clear.
  • "forceHttps": true redirects 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.required is 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

Was this article helpful?

Share your feedback

Cancel

Thank you!