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, streamlined instructions will be provided 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
				
			
				
					[secondary_label 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, nano is used:

				
					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:

				
					[label ~/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:

				
					[label ~/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:

				
					[secondary_label 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
				
			
				
					[secondary_label 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
				
			
				
					[secondary_label 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:

				
					[label 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:

				
					[label ~/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, you've used db as the 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:

				
					[label ~/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
				
			
				
					[secondary_label 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:

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