Skip to content
Guides

Deploy Laravel with queues and the scheduler

A Laravel app with MySQL, a Valkey queue, a queue:work worker, the scheduler every minute and migrations on each deploy, step by step.

Updated 17 Oct 20265 min read

By the end, Asha's Laravel shop duka runs on Tanzahost with its MySQL database, a Valkey for its queue and sessions, a worker that sends order emails from the queue, Laravel's scheduler every minute, and migrations that run before each new version goes live. It takes about fifteen minutes.

You need a Laravel 11 or newer app in a GitHub repository (or as a ZIP file), and a Tanzahost account with money in its wallet.

Step 1: prepare your composer.json

Ask for the PHP extensions your app needs in composer.json, and we add them on every build. For MySQL and a Valkey queue with Laravel's default Redis client, that's pdo_mysql and redis. Add pcntl too: with it, a worker stops cleanly between jobs when a deploy replaces it.

json
{
  "require": {
    "php": "^8.4",
    "laravel/framework": "^12.0",
    "ext-pcntl": "*",
    "ext-pdo_mysql": "*",
    "ext-redis": "*"
  }
}

Run composer update --lock so composer.lock agrees, then commit both files. Keep .env out of Git: its settings become encrypted variables in step 4.

We read the PHP version from "php" too. If your app builds CSS and JavaScript with Vite, we see the build script in package.json and run it on every build.

Step 2: create the project and the app

  1. In the dashboard, choose New project. Asha names it duka and picks the location closest to her customers, UK-01 · United Kingdom.

  2. On the project's page, in the Add a service card, choose App.

  3. Under Where's your code?, choose GitHub, pick the Repository and the Branch, and leave Build every time I push on.

  4. reading today's prices

  5. Name the app web, and choose Deploy.

Until GitHub is switched on

Connecting GitHub needs our GitHub app, which isn't set up yet, and the GitHub tab says so. Meanwhile, download your repository as a ZIP and choose Upload a ZIP instead.

Step 3: add MySQL and a Valkey for the queue

  1. On the project's page, in Add a service, choose Database, then MySQL. Name it mysql, and tick Add its connection details to web's variables.

  2. Choose Database again, then Valkey, named queue. Under What it's for, choose Queues and sessions: it never drops a key and saves every write. Tick web again.

web now has these variables, managed by the databases, and redeploys with them by itself:

Terminal
DB_CONNECTION=mysql
DB_HOST=mysql.duka.internal
DB_PORT=3306
DB_DATABASE=db_duka
DB_USERNAME=db_duka
REDIS_HOST=queue.duka.internal
REDIS_PORT=6379
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

DB_PASSWORD, REDIS_PASSWORD, DATABASE_URL and REDIS_URL are there too, with the passwords in them. Attach one Valkey to an app: two would fill the same REDIS_ names. See Connect an app to a database.

Step 4: set your other variables

Make Laravel's key on your computer:

Terminal
php artisan key:generate --show

Then open the project's Variables tab, choose web, and choose Paste a .env file. Paste the rest of your settings, like these, and choose Add them:

Terminal
APP_NAME=Duka
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:paste-the-key-you-just-made
APP_URL=https://duka.co.tz
LOG_CHANNEL=stderr

LOG_CHANNEL=stderr sends Laravel's log lines to your app's logs on Tanzahost, where you can search them. Leave the DB_ and REDIS_ lines out: the databases manage them. When the page says Redeploy to apply, choose Redeploy.

Step 5: run migrations on each deploy

Open the app. In the Laravel card, switch on Run migrations on each deploy, before the new version goes live. From then on, every deploy runs this once, in its own sandbox, after the build and before visitors switch to the new version:

Terminal
php artisan migrate --force

If a migration fails, the deploy stops and the version before keeps serving. The deploy's page shows the command's last lines, so you can see why. The same card runs one-off commands: Run migrations, Storage link, Clear cache, and Maintenance on and off.

Step 6: add the queue worker

In the app's Processes card, choose Add a worker or scheduled task:

  1. Kind: Worker.

  2. Name: queue.

  3. Command:

Terminal
php artisan queue:work --tries=3

Choose Save and restart. The worker runs from your app's live version, with its variables, and uses the app's limits unless you give it limits of its own. It pays only for what it uses.

Each deploy starts the new version's worker, then stops the old one, so the worker always runs your newest code: no queue:restart needed. If it stops by itself, it's started again; if it keeps crashing, 5 times in 10 minutes, we stop trying and tell you by email.

Step 7: add the scheduler

Choose Add a worker or scheduled task again:

  1. Kind: Scheduled task.

  2. Name: scheduler.

  3. Timetable: * * * * *, every minute.

  4. Command:

Terminal
php artisan schedule:run

Laravel then runs the tasks in your routes/console.php at their own times. Timetables are in UTC. A run still going at its next time is skipped, and one running for over an hour is stopped. The last 20 runs keep their output.

Step 8: check it works

  1. Open the project's Logs tab and choose web. Each visit shows with its status, and the worker's lines show as it takes jobs.

  2. Place a test order and watch the worker's line for the job.

  3. On the Processes card, the scheduler's last run shows when it ran and how long it took.

Your shop is live on its free address, like duka-7h3k.tanzahost.co. To put it on duka.co.tz, see Add a domain.

Optional: the same in tanzahost.json

The health address and the permanent folders can live in your code instead. storage is permanent for Laravel already: your uploads and logs survive every deploy.

json
{
  "health": "/up",
  "permanent": ["storage"]
}

Workers and scheduled tasks are set in the dashboard. See The settings file.