DevOps – Local Development Environment with Docker

It’s time to take the training wheels off (Replit.com) and create a local environment for our web application. We’re going to use a very useful tool called Docker to develop locally, and we’ll set up the PHP and Nginx part.

DevOps (development operations) is a very important part of web development. DevOps involves the configuration and setup of the environment for your application, whether that’s on your local computer or on the server that hosts your website.

Don’t be worried that this knowledge will go to waste. Many companies are starting to use Docker for local development and live.

The Docker setup in this lesson closely resembles the setup in this article with a couple major differences.

https://medium.com/@chewysalmon/laravel-docker-development-setup-an-updated-guide-72842dfe8bdf

The first step is to install Docker for Mac. After that’s done, create a folder called appone. This is where our web app and Docker files will live.

mkdir appone

cd appone

I like to put all docker-related files in a separate folder from my application files. Create another folder inside of the appone folder called docker.

mkdir docker

Create a docker-compose.yaml file inside of the appone folder.

touch docker-compose.yaml

Now, add the following code to the docker-compose.yaml file.

version: '3.8'
services:

    # Application
    phpapp:
        build:
            context: ./docker
            dockerfile: phpapp.dockerfile
        working_dir: /var/www
        volumes:
            - ./:/var/www

    # Web Server
    webserver:
        build:
            context: ./docker
            dockerfile: webserver.dockerfile
        working_dir: /var/www
        volumes:
            - ./:/var/www
        depends_on:
            - "phpapp"
        ports:
            - 80:80

The docker-compose.yaml file has all of the “services” that our application needs to run. A service is just a container. In Docker, there are Dockerfiles, images, and containers. You can use a Dockerfile to build an image, and you use an image to create a container. Containers are instances of images with an extra writable layer.

version: ‘3.8’

This is the version of Docker that we want to use.

services:

Services are just containers. Each service is a container. I’m not sure why they decided to call them services instead of containers. They probably called them services because each container represents a service that an application needs.

phpapp:

The first “service” that our application needs is PHP. It needs to be able to run PHP files. Again, the service is a container. The PHP container (service) is responsible for running PHP and a couple packages (like Image Magick for manipulating images).

build:

Each container needs an image. The container uses the image to create the environment that it needs to run the application. You can specify an existing image in the YAML file, or you can specify a Dockerfile under the “build” option.

context: ./docker

The location (context) of the build file is in the docker folder. I like putting docker-specific files in a separate folder, away from my application code.

dockerfile: phpapp.dockerfile

The name of the dockerfile that we will use to build the image that the container needs is called phpapp-dockerfile.

working_dir: /var/www

This sets the directory where commands will be run. It’s equivalent to using “cd” to go this directory.

volumes:

Use volumes to persist data.

– ./:/var/www

The data in the folder where the docker-compose.yaml file is will be connected to the www folder in the container.

    # Web Server
    webserver:
        build:
            context: ./docker
            dockerfile: webserver-dockerfile
        working_dir: /var/www
        volumes:
            - ./:/var/www
        depends_on:
            - "phpapp"
        ports:
            - 80:80

The only difference between the phpapp container and this container is the ports option. ports: Ports let us connect our host to the container. – 80:80 In this case, it connects our local port 80 to the container’s port 80.

The next step is to create the Dockerfiles for our phpapp and webserver containers.

Create the phpapp-dockerfile inside of the docker folder and paste the following code into it.

cd docker

touch phpapp.dockerfile

FROM php:8.0-fpm

# Install ImageMagick and MySQL driver
RUN apt-get update && apt-get install -y  \
    --no-install-recommends \
    libmagickwand-dev \
    && pecl install imagick \
    && docker-php-ext-enable imagick \
    && docker-php-ext-install pdo_mysql \
    && apt-get install -y --no-install-recommends zlib1g-dev libzip-dev unzip \
    && docker-php-ext-install zip

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

FROM php:8.0-fpm

Each command in our Dockerfile is a layer in the image. We start by using an image for php that has already been created.

RUN apt-get update && apt-get install -y  \
    --no-install-recommends \
    libmagickwand-dev \

apt-get update && apt-get install -y –no-install-recommends libmagickwand-dev

The two commands above are equivalent. I just put everything on the same line to make it easier to read.

    && apt-get install -y --no-install-recommends zlib1g-dev libzip-dev unzip \
    && docker-php-ext-install zip

These packages and extensions are important for using composer in your container.

If you try running “composer install”, you’ll get an error if you don’t have these tools (zlib1g-dev libzip-dev unzip) installed.

Failed to download kylekatarnls/update-helper from dist: The zip extension and unzip/7z commands are both missing, skipping.
Your command-line PHP is using multiple ini files. Run php --ini to show them.
Now trying to download from source

If we were running a normal server, “apt install zip unzip php-zip” probably would’ve been enough, but since this is in a Dockerfile, we have to use the specific “docker-php-ext-install” command to install the zip extension.

You might also get an error when trying to install tinker. Just run the following command. I’m not totally sure why that worked, but it did.

composer clearcache

After that, we install ImageMagick and an MySQL driver for later on when we create a database container.

RUN curl -sS https://getcomposer.org/installer | php — –install-dir=/usr/local/bin –filename=composer

I like to install composer in the container to make it easier to run composer commands.

Alternatively, you could use this command to run composer without having to install composer in our image.

docker run –rm -v “$(pwd)”:/app composer install

This uses the composer image to run composer, and then it deletes it.

We still have to create the image for NginX, our web server.

Create another file called webserver.dockerfile in the docker folder.

cd docker

touch webserver.dockerfile

FROM nginx:1.21

COPY vhost.conf /etc/nginx/conf.d/default.conf

RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log

COPY vhost.conf /etc/nginx/conf.d/default.conf

In this command, we’re copying the config file to the Nginx’s default config file. Since the context was already set to the docker folder, we don’t need to change directories. It will be able to find the vhost.conf file in the docker folder and copy it to the right directory in the container.

We’ll create the Nginx configuration file in the next lesson.