PHP Quickstart Guides

Laravel Quickstart Guide

This guide will show you how to set up a PHP app using the Laravel framework and MySQL or PostgreSQL. This guide leverages Dockerfile Deployment.

This guide is designed for Laravel 8 and later, although it may work with earlier versions as well.

Create an App

To create an App, use the Dashboard or the aptible apps:create CLI command:

# Set or substitute $APP_HANDLE with the name of your choice

aptible apps:create "$APP_HANDLE"

Until you push code and trigger a build, Aptible uses this App as a placeholder.

The aptible apps:create command will return the new App's Git Remote when it completes. Copy it, as you'll need it later.

Going forward, we'll refer to the App's handle as $APP_HANDLE, and its git remote as $GIT_REMOTE.

Provision a Database

Use the Dashboard or the aptible db:create CLI command to provision a 10GB Database:

  • PostgreSQL: aptible db:create "$DB_HANDLE" --type postgresql
  • MySQL: aptible db:create "$DB_HANDLE" --type mysql

Make sure you set or substitute $DB_HANDLE with the name of your choice.

The aptible db:create command will return a connection string when complete. This is a Database Credential for the new Database. You'll need it later to configure your App.

Going forward, we'll refer to this connection string as the $DATABASE_URL.

📘

Note

Databases are only reachable from within your Stack's internal network.

This means your Containers will be able to connect to your database, but if you want to connect from your workstation, you'll need to use a Database Tunnel.

Add a Dockerfile

A Dockerfile is a text file that contains the commands you would otherwise execute manually to build a Docker image. Aptible uses the resulting Image to run Containers for your App.

Your Dockerfile must adhere to the following rules to work with Dockerfile Deployment:

  1. The file must be named Dockerfile, starting with a capital letter, and no extension.
  2. It must be placed at the root of the repository.
  3. It must be committed to version control.

Here is a sample Dockerfile for a Laravel app. This will install your dependencies via Composer, and configure your app's public folder to be served by Apache:

FROM php:8.0-apache

WORKDIR /app

# Install dependencies
# git/zip/unzip is required by composer
# libpq-dev is required by PostgreSQL driver
RUN apt-get update && \
    apt-get install -y --no-install-recommends git zip unzip libpq-dev && \
    rm -rf /var/lib/apt/lists/* && \

    # Install php extensions
    docker-php-ext-install pdo_pgsql pdo_mysql && \

    # Install composer (https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md)
    composer_install='install-composer.php' && \
    expected_checksum="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" && \
    php -r "copy('https://getcomposer.org/installer', '${composer_install}');" && \
    actual_checksum="$(php -r "echo hash_file('sha384', '${composer_install}');")" && \
    ([ "$expected_checksum" = "$actual_checksum" ] || (echo "ERROR: Invalid installer checksum" && exit 1)) && \
    php "$composer_install" --install-dir '/usr/local/bin' --filename composer && \
    rm "$composer_install"

# Install dependencies via composer
ADD composer.json composer.lock ./
RUN composer install --no-dev --no-ansi --no-interaction --no-scripts --no-autoloader

# Add application files and install scripts and autoloader
ADD . ./
RUN composer install --no-dev --no-ansi --no-interaction

# The apache www-data user must be able to access project storage and cache
RUN chown -R www-data:www-data storage/ bootstrap/cache/ && \

    # Link public dir to apache public dir
    rm -rf /var/www/html && ln -s /app/public /var/www/html

Connect to the Database from your App

When deploying an app on Aptible, it is recommended to pass the Database's connection info, as well as any passwords or other sensitive configuration values, to your containers as environment variables via the App's Configuration. To do so with your Laravel app, you'll need to make a few tweaks to your config/database.php file.

First, add the following function at the very top of the file:

// NOTE: this function *must not* throw exceptions, otherwise Laravel
// will fail to boot. So, instead of throwing exceptions, we just
// return an intentionally invalid (empty) configuration if
// DATABASE_URL is not set.
function generateAptibleConnection() {
  if (getenv('DB_CONNECTION') !== 'aptible') {
    // If the DB_CONNECTION is not Aptible, then this won't be used,
    // and we should just bail out.
    return [];
  }
  $raw_url = getenv('DATABASE_URL');
  if (!$raw_url) {
    error_log('DB_CONNECTION is aptible, but DATABASE_URL is not set!');
    return [];
  }
  $url = parse_url($raw_url);
  $aptibleConnection = [
    'host'      => $url["host"],
    'port'      => $url["port"],
    'username'  => $url["user"],
    'password'  => $url["pass"],
    'database'  => substr($url["path"], 1),
    'charset'   => 'utf8',
    'prefix'    => '',
  ];
  $scheme = $url["scheme"];
  if ($scheme === "mysql") {
    // NOTE: The options below are required to run on Aptible, because
    // Aptible enforces SSL on connections to your MySQL database and
    // uses a self-signed certificate for MySQL (the latter due to
    // MySQL's poor security record). If you remove them, your app will
    // fail to connect to MySQL with an Access Denied error.
    $aptibleConnection['driver'] = 'mysql';
    $aptibleConnection['collation'] = 'utf8_unicode_ci';
    $aptibleConnection['options'] = [
      PDO::MYSQL_ATTR_SSL_CIPHER => 'DHE-RSA-AES256-SHA',
      PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
    ];
  } elseif ($scheme === "postgresql") {
    $aptibleConnection['driver'] = 'pgsql';
    $aptibleConnection['schema'] = 'public';
  } else {
    error_log("DB_CONNECTION is aptible and DATABASE_URL is set, but the scheme '$scheme' is invalid!");
    return [];
  }
  return $aptibleConnection;
}

Then, add the following key / value pair in your connections array:

'connections' => [
  'aptible' => generateAptibleConnection(),

  // Some other connections you already have
]

Once again, commit the changes.

Automate Database Migrations

Your app probably expects you to run database migrations upon deploy to ensure your app code and database are in sync.

You can tell Aptible to run your migrations by adding a .aptible.yml file in your repository.

The file must be named .aptible.yml exactly, found at the root of your repository, and committed to version control for Aptible to detect it.

Here is a sample .aptible.yml file to automate database migrations:

before_release:
  - php artisan migrate

Bring it all together

At this point, you're almost ready to deploy.

All that is left to do is put the pieces together by configuring your App to point it to your Database, then you'll be ready to push your code to Aptible.

To add the required environment variables, use the aptible config:set command as documented below. Make sure you set or substitute $APP_HANDLE and $DATABASE_URL with their proper values.

aptible config:set --app "$APP_HANDLE" \
  "DB_CONNECTION=aptible" \
  "DATABASE_URL=$DATABASE_URL" \
  "APP_DEBUG=false"

Once you're done, push your code to Aptible by adding Aptible as a git remote for your App, and then using git push to push your code:

git remote add aptible "$GIT_REMOTE"
git push aptible master

Deploy logs will stream to your terminal. They'll be useful in case anything goes wrong to understand the cause of the failure.

Add an Endpoint

At this point, your app is running on Aptible. Now we need to expose it on the Internet!

Follow the instructions here to proceed: How do I expose my web app on the Internet?.

Next steps

At this stage, your app should be running on Aptible. If you get an error when accessing your app, then you should check your logs via the aptible logs CLI command.

That being said, your app is not production ready yet. Here are a few recommended next steps.

Session Storage

By default, Laravel stores sessions on the local disk. However, on Aptible, container filesystesm are ephemeral so whenever a new Release is created for your App (i.e. whenever it's restarted, deployed, scaled, etc.), sessions stored on disk will be lost, and your users will be logged out. Additionally, if your App is scaled to multiple containers, each container will have its own session store.

Storing sessions in the database will allow them to persist across Releases and allow all of the App's containers to use the same session store. To store sessions in the database, you need to do two things.

  1. Create a database migration that creates the tables for your sessions by running php artisan session:table and commit the migration to version control. Since you automated database migrations, deploy the App now to update it and run all pending migrations.

  2. Instruct Laravel to use your database to store sessions by setting the SESSION_DRIVER environment variable:

    aptible config:set --app "$APP_HANDLE" \
            "SESSION_DRIVER=database"
    

Secret Key

Your app will be using the APP_KEY from your .env file, which is presumably your development APP_KEY.

You should generate a new one, and set it by running this command:

# Make sure you set or substitute $NEW_APP_KEY here!

aptible config:set --app "$APP_HANDLE" \
        "APP_KEY=$NEW_APP_KEY"