Skip to main content

Deploying Laravel on Vessl

Vessl detects Laravel automatically (by the presence of artisan in your repository root) and pre-fills sensible defaults for build, start, and post-deploy commands. This guide covers everything from the first deploy to production-level concerns like queues, storage, and scheduled tasks.

Prerequisites

  • A server added and provisioned in Vessl (Ubuntu, any size)
  • A GitHub repository with your Laravel app
  • PHP 8.1+ and Composer in your composer.json

What Vessl auto-detects

When Vessl sees artisan in your repo root it sets:

SettingAuto-filled value
Build engineNixpacks
Port8000
Install commandcomposer install (+ npm ci if package.json is present)
Build commandphp artisan migrate --force
Start commandphp artisan serve --host=0.0.0.0 --port=${PORT:-8000}
Post-deploy commandphp artisan migrate --force
Volume/app/storage (auto-mounted for persistent storage)

Vessl also auto-injects these environment variables on first deploy (only if you haven't set them yourself):

VariableValue
APP_KEYAuto-generated base64:… key
APP_ENVproduction
APP_URLYour app's public URL (https://…)
SESSION_DRIVERcookie (falls back if no DB add-on is attached)
CACHE_STOREarray (falls back if no DB add-on is attached)
QUEUE_CONNECTIONsync (falls back if no DB add-on is attached)

Once you attach a database add-on, Vessl sets the database-backed drivers automatically on the next deploy.

Step 1 — Create the app

  1. In your project, click New Application.
  2. Select your Laravel repository and branch.
  3. The wizard auto-detects Laravel — review the pre-filled settings and click Deploy.

The first deploy runs composer install, then php artisan migrate --force at build time, then starts php artisan serve. Storage is automatically mounted at /app/storage so uploaded files and logs survive restarts and redeploys.

Step 2 — Add a database

For any real app you'll want a persistent database. Open the app → Overview → Connected Services and click Add:

  1. Click Add add-on → PostgreSQL (or MySQL).
  2. Choose Deploy new to let Vessl provision a dedicated instance.
  3. Click Attach — Vessl provisions the database and injects these variables into your app:
DB_CONNECTION=pgsql
DB_HOST=<internal hostname>
DB_PORT=5432
DB_DATABASE=<db name>
DB_USERNAME=<username>
DB_PASSWORD=<password>
DATABASE_URL=postgresql://<user>:<pass>@<host>:5432/<db>

Laravel reads DB_CONNECTION, DB_HOST, etc. directly from .env — no config/database.php changes needed. The post-deploy command (php artisan migrate --force) runs automatically on every deploy.

Step 3 — Add Redis (optional)

For sessions, cache, or queues backed by Redis:

  1. Open the app → Overview → Connected Services → Add → Redis.
  2. Vessl injects:
REDIS_URL=redis://:<password>@<host>:6379
REDIS_HOST=<internal hostname>
REDIS_PORT=6379
REDIS_PASSWORD=<password>

Then update your env vars:

SESSION_DRIVER=redis
CACHE_STORE=redis
QUEUE_CONNECTION=redis

Step 4 — Queue workers

Laravel queues need a separate process running queue:work. In Vessl this is a daemon — a background process that runs alongside your web app on the same server.

  1. Go to the Daemons tab → Add Daemon.
  2. Set the command to:
    php artisan queue:work --sleep=3 --tries=3 --max-time=3600
  3. Set restart policy to always (so it restarts if it crashes).

The worker runs in its own container and shares environment variables with your web app, including the REDIS_* or DB_* connection details.

Step 5 — Scheduled tasks

Laravel's scheduler (php artisan schedule:run) needs to run every minute. Add a second daemon:

/bin/sh -c "while true; do php artisan schedule:run --no-interaction; sleep 60; done"

This is a simple shell loop that fires the scheduler every 60 seconds without needing cron access on the host.

Step 6 — Storage and file uploads

Vessl mounts /app/storage as a persistent volume automatically for Laravel apps. This means:

  • Uploaded files stored under storage/app/ survive redeploys and restarts.
  • Log files in storage/logs/ persist across container restarts.
  • The storage/ directory skeleton (framework/views, framework/cache, etc.) is bootstrapped on first mount.

To use storage:link for publicly accessible uploads, add it to your post-deploy command:

php artisan storage:link && php artisan migrate --force

Environment variables reference

Key variables you'll typically set manually:

VariableExampleNotes
APP_NAMEMy AppDisplay name
MAIL_MAILERsmtpMail driver
MAIL_HOSTsmtp.mailgun.org
MAIL_PORT587
MAIL_USERNAME
MAIL_PASSWORD
FILESYSTEM_DISKlocalSet to s3 for S3-backed storage

Common issues

php artisan key:generate is missing Vessl auto-generates APP_KEY on first deploy — you don't need to run this manually. If you see "No application encryption key has been specified", check that APP_KEY is set in the Variables tab.

php artisan migrate fails at build time The build step runs inside a container that may not have network access to the database. Move migrations to the post-deploy command instead of the build command, or use the pre-filled default which runs them post-deploy.

Storage permission errors Vessl bootstraps the storage/ skeleton, but if you see permission errors on storage/logs or storage/framework, add this to your post-deploy command:

chmod -R 775 storage bootstrap/cache && php artisan migrate --force

Sessions / cache not working after attaching a database Update SESSION_DRIVER, CACHE_STORE, and QUEUE_CONNECTION in the Variables tab to database or redis, then redeploy.

Vite / Mix assets not built If your package.json has a build script (Vite build or Mix prod), add it to your build command:

npm run build && php artisan migrate --force

Vessl detects package.json and includes npm ci in the install phase automatically.