Skip to content

Docker

Containers are an approach that allows us to isolate software environments without the need for full virtualization.

Docker is the must well known container solution, but it's not the only one.

Use Cases

There are several use cases for containers, but for our purposes we can focus on just two:

  • Development Environment creation and management
  • Software Deployment

Development Environments

Containers allow us to define a set of dependencies for our developement environments and make sure they are available no matter where we are using them.

We can develop a Laravel application without the need to install a specific PHP version on our machine. We can even have two Laravel projects with different versions (and diferent PHP dependencies) and they will not conflict with each other.

Deployment

Since containers allow us to isolate software dependencies they are a great option for software deployment, because we can define exactly what we need to have the software running and not have to worry about it conflicting with other applications deployed to the same server.

Concepts

This tutorial is not an in-deept view on containers or Docker, but we should still have some common vocabulary so here are the most relevant concepts.

Images, Containers, Networks and Volumes

An image is a static build of a set of depencies. For instance, in a Laravel project we might use a PHP image that has PHP version 8.1 and a set of PHP extensions needed for Laravel.

A container is a running image. Again in a Laravel project we could use the PHP 8.1 image to boot up a container that gets our code and runs it.

A network is the same concept we already know, the important part here is that Docker (and other container engines) can operate on a separate network to further isolate our environments. For us this means that we need to define ways of exposing the endpoints we want to connect to from our own network.

Finally a volume is the primary way that we use to persist data. By design containers are ephemeral, meaning that they are short lived and can be disposed of because we can just re-build the same thing at any time. This means that we need a way to persist data that is not present in the images. This also allows us to map our own storage as a volume in a container, thus allowing us the read/write to our hard drive and have those changes reflect on the container.

Dockerfile and Docker Hub

A Dockerfile is where we describe how to build an image. It has its own syntax, but essentially its a set of commands that install dependencies and define configurations.

This is the Dockerfile for Laravel that uses PHP 8.3:

FROM ubuntu:22.04

LABEL maintainer="Taylor Otwell"

ARG WWWGROUP
ARG NODE_VERSION=20
ARG MYSQL_CLIENT="mysql-client"
ARG POSTGRES_VERSION=17

WORKDIR /var/www/html

ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
ENV SUPERVISOR_PHP_USER="sail"

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
    echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
    echo "Acquire::BrokenProxy    true;" >> /etc/apt/apt.conf.d/99custom

RUN apt-get update && apt-get upgrade -y \
    && mkdir -p /etc/apt/keyrings \
    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano  \
    && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
    && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
    && apt-get update \
    && apt-get install -y php8.3-cli php8.3-dev \
       php8.3-pgsql php8.3-sqlite3 php8.3-gd \
       php8.3-curl \
       php8.3-imap php8.3-mysql php8.3-mbstring \
       php8.3-xml php8.3-zip php8.3-bcmath php8.3-soap \
       php8.3-intl php8.3-readline \
       php8.3-ldap \
       php8.3-msgpack php8.3-igbinary php8.3-redis \
       php8.3-memcached php8.3-pcov php8.3-imagick php8.3-xdebug php8.3-swoole \
    && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
    && apt-get update \
    && apt-get install -y nodejs \
    && npm install -g npm \
    && npm install -g pnpm \
    && npm install -g bun \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
    && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
    && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
    && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
    && apt-get update \
    && apt-get install -y yarn \
    && apt-get install -y $MYSQL_CLIENT \
    && apt-get install -y postgresql-client-$POSTGRES_VERSION \
    && apt-get -y autoremove \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3

RUN groupadd --force -g $WWWGROUP sail
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail

COPY start-container /usr/local/bin/start-container
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY php.ini /etc/php/8.3/cli/conf.d/99-sail.ini
RUN chmod +x /usr/local/bin/start-container

EXPOSE 80/tcp

ENTRYPOINT ["start-container"]
FROM ubuntu:22.04

LABEL maintainer="Taylor Otwell"

ARG WWWGROUP
ARG NODE_VERSION=20
ARG MYSQL_CLIENT="mysql-client"
ARG POSTGRES_VERSION=17

WORKDIR /var/www/html

ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC
ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
ENV SUPERVISOR_PHP_USER="sail"

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \
    echo "Acquire::http::No-Cache true;" >> /etc/apt/apt.conf.d/99custom && \
    echo "Acquire::BrokenProxy    true;" >> /etc/apt/apt.conf.d/99custom

RUN apt-get update && apt-get upgrade -y \
    && mkdir -p /etc/apt/keyrings \
    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano  \
    && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \
    && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
    && apt-get update \
    && apt-get install -y php8.3-cli php8.3-dev \
       php8.3-pgsql php8.3-sqlite3 php8.3-gd \
       php8.3-curl \
       php8.3-imap php8.3-mysql php8.3-mbstring \
       php8.3-xml php8.3-zip php8.3-bcmath php8.3-soap \
       php8.3-intl php8.3-readline \
       php8.3-ldap \
       php8.3-msgpack php8.3-igbinary php8.3-redis \
       php8.3-memcached php8.3-pcov php8.3-imagick php8.3-xdebug php8.3-swoole \
    && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \
    && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
    && apt-get update \
    && apt-get install -y nodejs \
    && npm install -g npm \
    && npm install -g pnpm \
    && npm install -g bun \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \
    && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
    && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \
    && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
    && apt-get update \
    && apt-get install -y yarn \
    && apt-get install -y $MYSQL_CLIENT \
    && apt-get install -y postgresql-client-$POSTGRES_VERSION \
    && apt-get -y autoremove \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3

RUN groupadd --force -g $WWWGROUP sail
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail

COPY start-container /usr/local/bin/start-container
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY php.ini /etc/php/8.3/cli/conf.d/99-sail.ini
RUN chmod +x /usr/local/bin/start-container

EXPOSE 80/tcp

ENTRYPOINT ["start-container"]

The images used on a Dockerfile must be accessible in some way. In the case of the previous Dockerfile it uses hte ubuntu:22.04 image (we can see this in the first line FROM ubuntu:22.04).By default Docker will connect to Docker Hub, a public container registry to download those images.

Docker Compose

Docker Compose is a way for defining a set of containers that work together and their configurations. This allows us to set up several containers for more complex environments. The simples example, and the won we will be using, is to have an application server (PHP in our case) and a database server (MySQL in our case).

This is the docker-compose.yaml file present in a Laravel instance using Sail.

yml
services:
    laravel.test:
        build:
            context: "./vendor/laravel/sail/runtimes/8.3"
            dockerfile: Dockerfile
            args:
                WWWGROUP: "${WWWGROUP}"
        image: "sail-8.3/app"
        extra_hosts:
            - "host.docker.internal:host-gateway"
        ports:
            - "8085:80"
        environment:
            WWWUSER: "${WWWUSER}"
            LARAVEL_SAIL: 1
            XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}"
            XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}"
            IGNITION_LOCAL_SITES_PATH: "${PWD}"
        volumes:
            - ".:/var/www/html"
        networks:
            - sail
        depends_on:
            - mysql
    mysql:
        image: "mysql/mysql-server:8.0"
        ports:
            - "${FORWARD_DB_PORT:-3306}:3306"
        environment:
            MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}"
            MYSQL_ROOT_HOST: "%"
            MYSQL_DATABASE: "${DB_DATABASE}"
            MYSQL_USER: "${DB_USERNAME}"
            MYSQL_PASSWORD: "${DB_PASSWORD}"
            MYSQL_ALLOW_EMPTY_PASSWORD: 1
        volumes:
            - "sail-mysql:/var/lib/mysql"
            - "./vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh"
        networks:
            - sail
        healthcheck:
            test:
                - CMD
                - mysqladmin
                - ping
                - "-p${DB_PASSWORD}"
            retries: 3
            timeout: 5s
networks:
    sail:
        driver: bridge
volumes:
    sail-mysql:
        driver: local
services:
    laravel.test:
        build:
            context: "./vendor/laravel/sail/runtimes/8.3"
            dockerfile: Dockerfile
            args:
                WWWGROUP: "${WWWGROUP}"
        image: "sail-8.3/app"
        extra_hosts:
            - "host.docker.internal:host-gateway"
        ports:
            - "8085:80"
        environment:
            WWWUSER: "${WWWUSER}"
            LARAVEL_SAIL: 1
            XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}"
            XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}"
            IGNITION_LOCAL_SITES_PATH: "${PWD}"
        volumes:
            - ".:/var/www/html"
        networks:
            - sail
        depends_on:
            - mysql
    mysql:
        image: "mysql/mysql-server:8.0"
        ports:
            - "${FORWARD_DB_PORT:-3306}:3306"
        environment:
            MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}"
            MYSQL_ROOT_HOST: "%"
            MYSQL_DATABASE: "${DB_DATABASE}"
            MYSQL_USER: "${DB_USERNAME}"
            MYSQL_PASSWORD: "${DB_PASSWORD}"
            MYSQL_ALLOW_EMPTY_PASSWORD: 1
        volumes:
            - "sail-mysql:/var/lib/mysql"
            - "./vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh"
        networks:
            - sail
        healthcheck:
            test:
                - CMD
                - mysqladmin
                - ping
                - "-p${DB_PASSWORD}"
            retries: 3
            timeout: 5s
networks:
    sail:
        driver: bridge
volumes:
    sail-mysql:
        driver: local

As we can see it describes two services, a Laravel application server and a MySQL server.

Commands

There are several ways of interacting with containers using Docker, these are some of the most commonly used commands:

bash

docker ps # shows the currently active containers

docker ps -a # shows active and inactive containers

docker images # shows a list of local images (downloaded from a registry or built locally)

docker image prune # deletes images that are not used by a container

docker system prune # deletes docker resources (images, stopped containers, volumes) that are not in use


docker-compose up # starts a set of containers based on a docker-compose file present in the current directory

docker-compose up -d # same as previous but in a detached form, meaning the console we still be available.

sail up
sail up -d
# same as docker-compose but wit some added benefits for Laravel Sail

docker ps # shows the currently active containers

docker ps -a # shows active and inactive containers

docker images # shows a list of local images (downloaded from a registry or built locally)

docker image prune # deletes images that are not used by a container

docker system prune # deletes docker resources (images, stopped containers, volumes) that are not in use


docker-compose up # starts a set of containers based on a docker-compose file present in the current directory

docker-compose up -d # same as previous but in a detached form, meaning the console we still be available.

sail up
sail up -d
# same as docker-compose but wit some added benefits for Laravel Sail

Additional Resources

This video is a full overview of Docker and containers.

Last updated:

IPLeiria | ESTG | EI | DAD 2023/24