Deploy Laravel on Bahriya

Laravel is a full-featured PHP web framework. To run it as a container you need a PHP runtime serving the application over HTTP on a known port. This guide uses an image that runs PHP-FPM behind nginx, listening on port 8080, and deploys it as an HTTP container.

Updated 3 Aug 20263 min read

Laravel is a full-featured PHP web framework. To run it as a container you need a PHP runtime serving the application over HTTP on a known port. This guide uses an image that runs PHP-FPM behind nginx, listening on port 8080, and deploys it as an HTTP container.

Only two things change between stacks on Bahriya: the Dockerfile that builds your image, and the port your application listens on. For the setup below, that port is 8080.

Dockerfile

This installs Composer dependencies, sets up PHP-FPM and nginx, and serves the application on port 8080. It uses a lightweight process manager to run both PHP-FPM and nginx in the foreground.

FROM php:8.3-fpm-alpine
 
# System packages and PHP extensions Laravel commonly needs
RUN apk add --no-cache nginx supervisor \
    && docker-php-ext-install pdo pdo_mysql bcmath
 
# Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
 
WORKDIR /var/www/html
 
# Install dependencies first for better build caching
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
 
# Copy the application and finish the autoloader
COPY . .
RUN composer dump-autoload --optimize \
    && chown -R www-data:www-data storage bootstrap/cache
 
# nginx listens on 8080 and forwards PHP to FPM; supervisor runs both
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/supervisord.conf /etc/supervisord.conf
 
EXPOSE 8080
 
CMD ["supervisord", "-c", "/etc/supervisord.conf"]

Configure your nginx server block to listen 8080; with a root of /var/www/html/public, forwarding .php requests to PHP-FPM on 127.0.0.1:9000. Point the health check at a route your app answers with a 2xx.

An alternative for simple deployments is a single-command image that runs php artisan serve --host=0.0.0.0 --port=8080, though the FPM-plus-nginx setup is the more robust production choice.

Application settings

  • APP_KEY — Laravel needs an application key for encryption and signed cookies. Generate one with php artisan key:generate --show and supply it as a secret rather than committing it.
  • APP_ENV / APP_DEBUG — set APP_ENV=production and APP_DEBUG=false in production.
  • Config and route caching — for a faster boot, run php artisan config:cache and php artisan route:cache as part of your build. Only cache config once your environment values are available at build time; otherwise cache them at container start instead.
  • Storage — Laravel writes to storage/ for logs, sessions, and cached views. Ensure it is writable (the Dockerfile above sets ownership). For a filesystem that must survive restarts, attach a persistent volume; see persistent storage.

The .env and secrets

Laravel reads configuration from environment variables (the .env file locally). On Bahriya you do not ship a .env file — you supply the same keys as environment variables and secrets:

  • Non-sensitive keys such as APP_ENV, APP_URL, and LOG_CHANNEL as plain environment variables.
  • Sensitive keys such as APP_KEY, DB_PASSWORD, and third-party API keys as secrets, encrypted at rest and injected at runtime.

Deploy with the Reis CLI

reis container:create \
  --type http \
  --name "Laravel App" \
  --handle laravel-app \
  --image ghcr.io/myorg/laravel-app:v1.0.0 \
  --project my-project \
  --port 8080 \
  --healthcheck /up \
  --cpu 500 \
  --memory 512 \
  --regions falkenstein-1 \
  --env APP_ENV=production \
  --env APP_DEBUG=false \
  --secrets APP_KEY=laravel-app-key \
  --secrets DB_PASSWORD=db-password

See Deploy an HTTP container with flags for the full flag reference and Deploy your first container for the Console walkthrough.

Databases and migrations

Bahriya does not host your database — connect Laravel to your own managed database using host, database, user, and password values supplied through secrets. Run php artisan migrate --force as a one-off before or after a deploy rather than inside the web process; the same image with a migrate command works well for this.

Next steps