Dockerized Laravel Rest API

Dockerized Laravel Rest API

Introduction

To containerize an application refers to the process of adapting an application and its components in order to be able to run it in lightweight environments known as containers.

I am going to use Docker Compose to containerize a Laravel application for development.

  • An app service running PHP7.4-FPM;
  • db service running MySQL 5.7;
  • An nginx service that uses the app service to parse PHP code before serving the Laravel application to the final user.

Prerequisites

Step 1 — Laravel App

composer create-project --prefer-dist laravel/laravel myapi
cd myapi

In the next step, we’ll create a .env configuration file to set up the application.

Step 2 — Setting Up the Application’s .env File

cp .env.example .env

Open this file using nano or your text editor of choice:

nano .env
APP_NAME=myapi
APP_ENV=dev
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=myapi
DB_USERNAME=root
DB_PASSWORD=root
...

Feel free to also change the database name, username, and password, if you wish. These variables will be leveraged in a later step where we’ll set up the docker-compose.yml file to configure our services.

Step 3 — Setting Up the Application’s Dockerfile

Although both our MySQL and Nginx services will be based on default images obtained from the Docker Hub, we still need to build a custom image for the application container. We’ll create a new Dockerfile for that.

Our myapi image will be based on the php:7.4-fpm official PHP image from Docker Hub. On top of that basic PHP-FPM environment, we’ll install a few extra PHP modules and the Composer dependency management tool.

Create a new Dockerfile with:

nano Dockerfile

Copy the following contents to your Dockerfile: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

Step 4 — Setting Up Nginx Configuration and Database Dump Files

We’ll now set up a folder with files that will be used to configure and initialize our service containers.

To set up Nginx, we’ll share a myapi.conf the file that will configure how the application is served. Create the docker-compose/nginx folder with:

mkdir -p docker-compose/nginx

Open a new file named myapi.conf within that directory:

nano docker-compose/nginx/myapi.conf

Copy the following Nginx configuration to that file:docker-compose/nginx/myapi.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;
    }
}

Step 5 — Creating a Multi-Container Environment with Docker Compose

We’ll define three different services in our docker-compose.yml file: appdb, and nginx.

The app service will build an image called myapi, based on the Dockerfile we’ve previously created. The container defined by this service will run a php-fpm server to parse PHP code and send the results back to the nginx service, which will be running on a separate container. The mysql service defines a container running a MySQL 5.7 server. Our services will share a bridge network named myapi.

The application files will be synchronized on both the app and the nginx services via bind mountsBind mounts are useful in development environments because they allow for a performant two-way sync between host machine and containers.

Create a new docker-compose.yml file at the root of the application folder:

nano docker-compose.yml

A typical docker-compose.yml file starts with a version definition, followed by a services node, under which all services are defined. Shared networks are usually defined at the bottom of that file.

To get started, copy this boilerplate code into your docker-compose.yml file:docker-compose.yml

We’ll now edit the services node to include the appdb and nginx services.

The app Service

The app service will set up a container named myapi-app. It builds a new Docker image based on a Dockerfile located in the same path as the docker-compose.yml file. The new image will be saved locally under the name myapi.

Even though the document root being served as the application is located in the nginx container, we need the application files somewhere inside the app container as well, so we’re able to execute command line tasks with the Laravel Artisan tool.

Copy the following service definition under your services node, inside the docker-compose.yml file:docker-compose.yml

These settings do the following:

version: "3.3"
services:
  app:
    build:
      args:
        user: myapi
        uid: 1000
      context: ./
      dockerfile: Dockerfile
    image: myapi
    container_name: myapi-app
    restart: unless-stopped
    working_dir: /var/www/
    volumes:
      - ./:/var/www
    networks:
      - myapi

  db:
    image: mysql:5.7
    container_name: myapi-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_USER: ${DB_USERNAME}
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    volumes:
      - ./docker-compose/mysql:/docker-entrypoint-initdb.d
    networks:
      - myapi

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

networks:
  myapi:
    driver: bridge
 

Make sure you save the file when you’re done.

Step 6 — Running the Application with Docker Compose

We’ll now use docker-compose commands to build the application image and run the services we specified in our setup.

Build the app image with the following command:

docker-compose build app

This command might take a few minutes to complete. You’ll see output similar to this:

When the build is finished, you can run the environment in background mode with:

docker-compose up -d
OutputCreating myapi-db    ... done
Creating myapi-app   ... done
Creating myapi-nginx ... done

This will run your containers in the background. To show information about the state of your active services, run:

docker-compose ps

You’ll see output like this:

Your environment is now up and running, but we still need to execute a couple commands to finish setting up the application. You can use the docker-compose exec command to execute commands in the service containers, such as an ls -l to show detailed information about files in the application directory:

docker-compose exec app ls -l

We’ll now run composer install to install the application dependencies:

docker-compose exec app composer install

The last thing we need to do before testing the application is to generate a unique application key with the artisan Laravel command-line tool. This key is used to encrypt user sessions and other sensitive data:

docker-compose exec app php artisan key:generate

Now go to your browser and access your server’s domain name or IP address on port 8000:

http://server_domain_or_IP:8000

You’ll see a page like this:

Leave a Reply

Close Menu