HCode builds a Laravel app without a Dockerfile. It installs your PHP and Node.js packages, builds the Vite assets, and when the app starts it runs your migrations before it serves requests. You set a few environment variables before the first deploy.
How HCode recognises Laravel
A repository is a Laravel app when composer.json requires laravel/framework, or when an artisan file sits next to composer.json or index.php. HCode checks for Laravel before package.json, so the Vite package.json that comes with every new Laravel app doesn't make it look like a static site. Only a Dockerfile is checked earlier; see which check wins.
The startup steps below, and serving the public folder, depend on the artisan file at the root of the app. A Laravel app without one is built and served like plain PHP.
The plan suggests a web app on port 3000 with no start command. Leave Start command empty. The app's own startup runs the migrations and the web server, and a start command you set replaces all of it.
What the build does
- Installs PHP. The version comes from
require.phpincomposer.json, so^8.3gives PHP 8.3. Without one, it's PHP 8.4. - Installs PHP extensions: the ones Laravel needs, every
ext-*entry incomposer.json'srequire, the database driver forDB_CONNECTION(pgsqlormysql), and Redis whenREDIS_URLorREDIS_HOSTis set or a Composer package name containsredis. - Runs
composer install --optimize-autoloader --no-scripts --no-interaction. Your Composer scripts don't run during the build. - If there is a
package.json, installs your Node.js packages and runs itsbuildscript. In a standard Laravel app that isvite build, so the compiled CSS and JavaScript ship with the app. - If there is a
package.json, also creates thestorageandbootstrap/cachefolders and runsphp artisan config:cache,event:cache,route:cacheandview:cache.
Your environment variables are available to the build, and some of them change what it installs. Set DB_CONNECTION, and REDIS_URL if you use Redis, before the first deploy. If you add or change one later, deploy again: Apply settings and Restart reuse the last build, which doesn't have the new driver. Variables starting with VITE_ are written into the assets, so they need a new build too (see When changes take effect).
Your database can't be reached during the build, only once the app runs. See Databases during the build.
What happens when the app starts
Each time the app starts, including after a deploy or a restart, it runs:
php artisan migrate --force
php artisan storage:link
php artisan optimize:clear
php artisan optimize
Then FrankenPHP serves the public folder on the port in PORT, which is 3000 unless you change Runtime port. If any of these commands fails, the app stops before it serves anything and the deploy fails. The runtime logs show the error.
php artisan optimize caches the configuration again from the app's current environment variables, so a changed variable takes effect on the next restart.
A deploy starts the new release before it stops the old one, so the new release's migrations run while the previous release is still serving. A migration that drops or renames a column the old code uses can break it while both are running. Add the new column in one deploy and remove the old one in a later one.
A Start command of your own, or a web: line in a Procfile, replaces this startup. None of the four commands run, and your command has to start the web server itself.
Skip the migrations
Add this environment variable, then restart the app or deploy again:
RAILPACK_SKIP_MIGRATIONS=true
The value must be exactly true. The other three commands still run. Run your migrations another way, for example from your own machine with external access to the database.
Environment variables to set
Add these under Settings → Environment variables, or in Environment variables when you create the project. See Environment variables.
| Variable | Value |
|---|---|
APP_KEY |
Generated for you when you create the project in the dashboard. Otherwise, the output of php artisan key:generate --show |
DB_CONNECTION |
pgsql for an HCode database |
DB_URL |
The database's internal connection string |
APP_KEY
Laravel encrypts sessions and cookies with APP_KEY and won't serve requests without one.
When you create the project in the dashboard from a GitHub repository and HCode recognises Laravel, HCode generates the key and stores it as a secret. The create form shows an APP_KEY row that says so. To use a key of your own, type it in that row.
In every other case you set the key yourself: a project created from an uploaded ZIP or through the API, and a project that already exists. Generate it on your own machine, in the app's folder:
php artisan key:generate --show
It prints a value that starts with base64:. Add the whole value as APP_KEY and tick Secret.
Keep the same key from then on: a new key signs everyone out and leaves anything encrypted with the old key unreadable. A secret's value isn't shown again after it is saved, so you can't copy a generated key into another project. If two projects need the same key, such as the scheduler below, generate one yourself and set it in both.
Without it, the deploy fails and "What went wrong" says "Laravel needs an APP_KEY."
The database
HCode databases are PostgreSQL. Create one on the project's Databases tab (see Databases overview), copy its internal connection string, and add:
DB_CONNECTION=pgsql
DB_URL=postgres://u1a2b3c4d:dbp_xxxxxxxx@db-0a1b2c3d4e5f:5432/db_my-app?sslmode=disable
Use the string from your own database card, and mark DB_URL as Secret. If you'd rather use separate variables, set DB_HOST, DB_PORT (5432), DB_DATABASE, DB_USERNAME and DB_PASSWORD from the parts of the string.
DB_CONNECTION matters twice. The build reads it to install the PHP driver, and a new Laravel app uses SQLite when it isn't set. An SQLite file lives inside the app's container, and each deploy or restart starts a fresh one, so the data doesn't last.
For a MySQL database hosted somewhere else, DB_CONNECTION=mysql installs the MySQL driver instead.
Sessions and cache
A new Laravel app keeps sessions and cache in the database, in the sessions and cache tables its migrations create. The migrations at startup create them on the first deploy. You have three choices:
| Setting | What you need | What happens on a deploy or restart |
|---|---|---|
| Database (the default) | A database and the migrations | Sessions and cache are kept |
SESSION_DRIVER=file and CACHE_STORE=file |
Nothing | Each deploy or restart starts a fresh copy of the app, so the files are gone and everyone is signed out |
SESSION_DRIVER=redis and CACHE_STORE=redis |
An HCode Redis database, with its connection string in REDIS_URL |
Sessions and cache are kept |
File sessions suit an app with no database. Setting REDIS_URL before the build also installs PHP's Redis extension.
Scheduled commands
HCode can't run commands on a schedule yet. Scheduled commands for a project are planned. Until then, run Laravel's scheduler as a second project, a background worker that keeps php artisan schedule:work running. It checks your schedule every minute and runs the commands that are due.
- Create a new project from the same repository and branch.
- Under App type, choose Background worker.
- Set Start command to
php artisan schedule:work. - In Optional settings, under Environment variables, add the same variables as your web app. Type the web app's key in the
APP_KEYrow: left empty, HCode generates a different key for this project, and the scheduler can't read anything the web app encrypted. - Give it its own database connection (below), then create and deploy it.
The database's internal connection string only works inside its own project, so the scheduler can't use it. Turn on external access for the web app's database, create a token for the scheduler, and set DB_URL in the scheduler to the connection string the token comes with. That string ends in sslmode=require, which external connections need. When the token expires the scheduler loses its database, so choose Never under Expires in, or replace the token before it runs out.
Redis has no external access, so the scheduler can't reach the web app's Redis database. If your web app keeps its cache in Redis, set CACHE_STORE=database in the scheduler.
The scheduler's start command replaces the startup steps, so it doesn't run migrations or php artisan optimize; the web app does that. When the repository has a package.json, the build caches the configuration, so after changing one of the scheduler's variables, deploy it again rather than restarting it. It counts toward your plan like any other project.
A queue worker works the same way, with php artisan queue:work as its start command.