Back to Blog
Tutorial11 min read2026-06-29

How to Deploy Laravel to the Cloud (Docker + MySQL)

A complete guide to deploying Laravel to the cloud with Docker, PHP-FPM + Nginx, a managed MySQL database, queue workers, scheduled tasks, and the production config that actually matters.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Laravel is a joy to develop with and a little fiddly to deploy — PHP-FPM, an Nginx front end, queue workers, the scheduler, and config caching all need attention. This guide covers a clean, containerized production deployment backed by a managed MySQL database.

The Laravel runtime model

A Laravel app in production has more moving parts than a single process:

  • PHP-FPM runs your application code.
  • A web server (Nginx) serves static assets and proxies PHP requests to FPM.
  • A queue worker processes jobs (php artisan queue:work).
  • A scheduler runs php artisan schedule:run every minute (via cron).

In a container world you can run these as separate services or combine the web tier into one image. Let's build the web image first.

A production Dockerfile

FROM php:8.3-fpm-alpine

RUN apk add --no-cache nginx supervisor \
    && docker-php-ext-install pdo pdo_mysql bcmath opcache

WORKDIR /var/www/html

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts

COPY . .
RUN composer dump-autoload --optimize \
    && php artisan config:cache \
    && php artisan route:cache \
    && php artisan view:cache

EXPOSE 8080
CMD ["sh", "-c", "php-fpm -D && nginx -g 'daemon off;'"]

The --no-dev --optimize-autoloader flags strip dev dependencies and build a classmap for faster autoloading. The config:cache, route:cache, and view:cache commands compile your config into single fast-loading files — a meaningful production speedup. Important: once config is cached, env() only works inside config files, so make sure all runtime config reads from config().

Enable OPcache in production — it caches compiled PHP bytecode and is one of the biggest single performance wins for any PHP app.

Environment and the app key

Laravel needs APP_KEY set or it can't decrypt sessions and cookies. Generate it once and store it as a secret:

php artisan key:generate --show

Set these in production:

APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:...
DB_CONNECTION=mysql
LOG_CHANNEL=stderr

APP_DEBUG=false is non-negotiable — debug mode leaks stack traces and environment data. LOG_CHANNEL=stderr sends logs to stdout/stderr so your platform's log aggregation captures them.

Managed MySQL

Use a managed MySQL instance and feed Laravel a DATABASE_URL or the individual DB_* vars. Run migrations as a deploy step:

php artisan migrate --force

The --force flag skips the interactive confirmation Laravel shows in production. Use the expand/contract pattern for zero-downtime schema changes.

Queues and the scheduler

Queue workers and the scheduler run as separate processes from your web tier.

Queue worker (a separate container/service):

php artisan queue:work --tries=3 --max-time=3600

--max-time recycles the worker periodically to avoid memory creep — a known issue with long-lived PHP workers.

Scheduler — Laravel's scheduler expects a cron entry hitting it every minute:

* * * * * php /var/www/html/artisan schedule:run

On a platform with managed cronjobs you can run php artisan schedule:run on a one-minute schedule instead of maintaining your own crontab.

Deploying on PandaStack

  1. 1Create a MySQL database (5.7 or 8.x). Connection details are injected as env vars.
  2. 2Connect your repo as a container app; PandaStack detects the Dockerfile and builds it via rootless BuildKit.
  3. 3Set APP_KEY, APP_ENV=production, APP_DEBUG=false, and DB vars in the dashboard.
  4. 4Add php artisan migrate --force as a release command.
  5. 5Create a cronjob running php artisan schedule:run every minute, and a second service for php artisan queue:work.

You get automatic SSL, live build logs, rollbacks, and deploy history.

ComponentHow it runs
Web (PHP-FPM + Nginx)Container app
DatabaseManaged MySQL
Queue workerSeparate container service
SchedulerCronjob, every minute
MigrationsRelease command

Common pitfalls

  • APP_DEBUG=true in production — leaks sensitive data.
  • Forgetting --force on migrate — the deploy hangs on a confirmation prompt.
  • Caching config but reading env() at runtime — returns null; read from config() instead.
  • No queue worker — queued jobs silently never run.
  • Writable storage/ and bootstrap/cache/ — make sure the runtime user can write these.

References

  • Laravel deployment docs: https://laravel.com/docs/12.x/deployment
  • Laravel queues: https://laravel.com/docs/12.x/queues
  • Laravel task scheduling: https://laravel.com/docs/12.x/scheduling
  • PHP OPcache configuration: https://www.php.net/manual/en/book.opcache.php
  • Official PHP Docker images: https://hub.docker.com/_/php

---

PandaStack's free tier includes container apps, a managed MySQL database, and cronjobs — everything a Laravel app needs, with automatic SSL and live logs. Connect your repo and deploy at https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also