Tutorial

How To Bootstrap a New Laravel Application with Docker Compose

PHPDockerLaravel
Part of the Series: How To Build a Links Landing Page in PHP with Laravel and Docker Compose

Laravel is an open-source PHP framework that provides a set of tools and resources to build modern PHP applications. In this project-based tutorial series, you’ll build a Links Landing Page application with the Laravel framework, using a containerized PHP development environment managed by Docker Compose.

At the end, you’ll have a one-page website built with Laravel and managed via Artisan commands where you can share relevant links to an audience on social channels and presentations.

To get started, you’ll need to create a containerized environment able to execute PHP and Composer, the PHP dependency management tool. Then, you’ll be able to bootstrap the new Laravel application from scratch, without the need to have a local PHP environment installed on your local machine or development server.

In this guide, we’ll provide streamlined instructions on how to set this environment up based on our tutorial on How To Install Laravel with Docker Compose on Ubuntu 20.04. Please refer to that tutorial for more detailed instructions on each of the options used within the Docker Compose file that will be provided in this guide.

Create a new directory for your application in your home folder:

  • mkdir ~/landing-laravel
  • cd ~/landing-laravel

Next, you’ll create the docker-compose.yml file that will define the containerized environment. In this file, you’ll set up a service named app, which will be based on a custom Docker image built with a Dockerfile you’ll set up later on.

The build arguments user and uid, both defined in the docker-compose.yml file and used in the Dockerfile at build time, should be changed to reflect your own username and uid on your local machine or development server. To find out your current user’s uid, type:

  • echo $UID
Output
1000

The user and uid variables will be available at build time and will be used in the Dockerfile to create a new user in the app service with the same username and uid as your current system user on your local machine or development server. This will avoid permission and ownership issues when working with application files both from the container as well as from the host that executes Docker.

Create a new docker-compose.yml file using your text editor of choice. Here, we’re using nano:

  • nano docker-compose.yml

Copy the following content to this file, and don’t forget to replace the highlighted values with appropriate values depending on your own username and uid on the system that runs Docker:

~/landing-laravel/docker-compose.yml
version: "3.7"
services:
  app:
    build:
      args:
        user: sammy
        uid: 1000
      context: ./
      dockerfile: Dockerfile
    image: landing-app
    restart: unless-stopped
    working_dir: /var/www/
    volumes:
      - ./:/var/www
    networks:
      - landing

networks:
  landing:
    driver: bridge

Save and close the file when you are done. If you are using nano, you can do that by pressing CTRL+X, then Y and ENTER to confirm.

Next, you’ll set up the Dockerfile that is referenced in the docker-compose.yml file, which will set up a custom image for the app service:

  • nano Dockerfile

This Dockerfile extends from the default php:7.4-fpm Docker image. It uses the user and uid variables to create a new user able to execute Artisan and Composer commands. It also installs a few PHP dependencies that are required by Laravel, and the Composer executable.

Copy the following content to your Dockerfile:

~/my-todo-list/Dockerfile
FROM php:7.4-fpm

# Arguments defined in docker-compose.yml
ARG user
ARG uid

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip

# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*

# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

# Get latest Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Create system user to run Composer and Artisan Commands
RUN useradd -G www-data,root -u $uid -d /home/$user $user
RUN mkdir -p /home/$user/.composer && \
    chown -R $user:$user /home/$user

# Set working directory
WORKDIR /var/www

USER $user

Save and close the file when you’re done. Next, you can bring your environment up with:

  • docker-compose up -d

This command will execute Docker Compose in detached mode, which means it will run in the background. The first time you bring an environment up with a custom image, Docker Compose will automatically build the image for you before creating the required containers. This might take a few moments to finish. You’ll see output similar to this:

Output
Creating network "landing-laravel_landing" with driver "bridge" Building app Step 1/11 : FROM php:7.4-fpm ---> fa37bd6db22a ... Step 10/11 : WORKDIR /var/www ---> Using cache ---> 769afd5d44d8 Step 11/11 : USER $user ---> Using cache ---> 841eb5852b69 Successfully built 841eb5852b69 Successfully tagged landing-app:latest WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Creating landing-laravel_app_1 ... done

You can verify that your environment is up and running with:

  • docker-compose ps
Output
Name Command State Ports ------------------------------------------------------------------------ landing-laravel_app_1 docker-php-entrypoint php-fpm Up 9000/tcp

Once the app service is up, you can run Composer, the PHP dependency management tool, to bootstrap a new Laravel application. In order to do that, you’ll use docker compose exec to run commands on the app service, where PHP is installed.

The following command will use Docker Compose to execute composer create-project, which will bootstrap a fresh installation of Laravel based on the laravel/laravel package:

  • docker-compose exec app composer create-project laravel/laravel --prefer-dist application
Creating a "laravel/laravel" project at "./application"
Installing laravel/laravel (v8.4.0)
  - Downloading laravel/laravel (v8.4.0)
  - Installing laravel/laravel (v8.4.0): Extracting archive
Created project in /var/www/application
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies
Lock file operations: 104 installs, 0 updates, 0 removals
…
Package manifest generated successfully.
71 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan key:generate --ansi
Application key set successfully.

This installation creates a new .env file based on the default .env.example file that comes with Laravel. The .env file contains database credentials and other sensitive application settings, and should be unique per environment where the app runs. You’ll come back to edit this file after you finish setting up the development environment.

Next, copy the application files to the same directory as the docker-compose.yml file, so that you can share Laravel’s environment variables file with Docker Compose. Then, you can remove the application directory created by Composer:

cp -rT application .
rm -rfv application

Your application is now bootstrapped, but you’ll need to include a couple services in the Docker Compose file in order to be able to access the app from a browser. An nginx service will serve the application using the Nginx web server, and a db service will host the application’s MySQL database.

First, bring your environment down with:

  • docker-compose down
Output
Stopping landing-laravel_app_1 ... done Removing landing-laravel_app_1 ... done Removing network landing-laravel_landing

This will remove all containers and networks associated with this environment. Before editing your docker-compose.yml file to add the new services, create a new directory to share configuration files with containers. You’ll need this to properly set up Nginx to handle the Laravel PHP application.

  • mkdir -p docker-compose/nginx

Next, create a new landing-laravel.conf file containing a custom Nginx server block. Later on, you’ll set up a volume to share this file within the nginx service container.

Open a new Nginx configuration file with:

  • nano docker-compose/nginx/landing-laravel.conf

The following server block configures Nginx to serve a Laravel application using an external service (app) to handle PHP code. Copy this content to your own Nginx configuration file:

docker-compose/nginx/landing-laravel.conf
server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}

Save and close the file when you’re done.

Next, open your docker-compose.yml file:

  • nano docker-compose.yml

Include the following configuration for the nginx service, at the same level as the previously configured app service. This will create a new service based on the nginx:alpine image, and all requests on port 8000 of the host where Docker is running will be redirected to port 80 in the service container. In addition to the application files, you’ll also share a volume containing Nginx’s configuration file for a Laravel application:

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - 8000:80
    volumes:
      - ./:/var/www
      - ./docker-compose/nginx:/etc/nginx/conf.d/
    networks:
      - landing

Then, include the following configuration block for the db service. This will create a service based on the default MySQL 8 image, and pull in the values defined in Laravel’s environment file to set up database access:

  db:
    image: mysql:8
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USERNAME}
    networks:
      - landing

This is how your updated docker-compose.yml file should look like once you’re finished:

~/landing-laravel/docker-compose.yml
version: "3.7"
services:
  app:
    build:
      args:
        user: sammy
        uid: 1000
      context: ./
      dockerfile: Dockerfile
    image: landing-app
    restart: unless-stopped
    working_dir: /var/www/
    volumes:
      - ./:/var/www
    networks:
      - landing

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - 8000:80
    volumes:
      - ./:/var/www
      - ./docker-compose/nginx:/etc/nginx/conf.d/
    networks:
      - landing
  db:
    image: mysql:8
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USERNAME}
    networks:
      - landing

networks:
  landing:
    driver: bridge

Note: for more detailed information about containerizing Laravel environments, including explanations about shared volumes and networks, please refer to our full guide on How To Install Laravel with Docker Compose on Ubuntu 20.04.

Save and close the file when you’re done editing. Lastly, update your Laravel dot env file (.env) to point the MySQL database host configuration to the host where the MySQL service will be running, called db:

  • nano .env

The .env file that is automatically generated by Composer upon installation comes with some default values that you might want to change, such as the APP_NAME and the APP_URL. The database DB_HOST variable must be changed to point to the service where MySQL will be running, and you can reference it by its service name, as defined in the docker-compose.yml file. In this example, we’ve used db as name for the database service, so this will be available in the containerized network as a host named db.

Change your .env accordingly, using the following example as base. The highlighted values were updated here to reflect the state of the application under development:

~/landing-laravel/.env
APP_NAME=LandingLaravel
APP_ENV=local
APP_KEY=base64:ffYPNP8kPeQDf8gE/qh3kWjk59p6gFY66kCKhhKUa2w=
APP_DEBUG=true
APP_URL=http://localhost:8000

LOG_CHANNEL=stack
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=landing-db
DB_USERNAME=landing-user
DB_PASSWORD=dev-password

...

You don’t need to change any other sections of this file, but feel free to tweak to your specific use case.

Save and close the file when you’re done editing its contents.

You can now bring the updated environment up with:

  • docker-compose up -d
Output
Creating network "landing-laravel_landing" with driver "bridge" Creating landing-laravel_app_1 ... done Creating landing-laravel_db_1 ... done Creating landing-laravel_nginx_1 ... done

With the full environment up, you can now point your browser to localhost or your remote server’s IP address, on port 8000:

http://localhost:8000

If everything works as expected, you’ll see a page like this:

Laravel Landing Links - basic app

In the next part of this series, you’ll create a database migration to set up a links table.

Creative Commons License