Save 37% off PRO during our Black Friday Sale! »

Deploy PHP Apps with Docker - The Essentials

Deploy PHP Apps with Docker - The Essentials

So, why deploy with Docker, especially when there are so many other deployment options?

I'll speak for myself and say that in my experience, after learning enough about Docker, and getting just a few more grey hairs, I've found Docker to be a simpler, more homogeneous way of deploying than I'd been used to in the past, using tools such as Rocketeer, Capistrano, and Rsync over SSH.

I've found that by using some standardised files and a small set of commands, I can deploy an application almost as easily to production as I can on my local development machine.

In this talk, I want to share what I've learned about Docker in the hope that what I've learned can help others. Enjoy!

If you'd like to dive far deeper, I've written a free book called Docker Essentials, which I based this talk on. It covers the topic in a lot more detail than the talk does, yet without going overboard. It's packed with tips, tricks, and pointers that I've learned along the way. If you're interested, you can download a copy, for free, from https://dockeressentials.com.

587223eccffb4d264fd18bb9d54cd1d9?s=128

Web Dev with Matt

November 19, 2021
Tweet

Transcript

  1. By Matthew Setter / November 18, 2021 Deploy PHP Apps

    with Docker The Essentials
  2. Nice to meet you • @settermjd • Been coding since

    around 1996 • Mostly written web-based apps • Once written VB + Access apps • Used Docker since 2015 I’m Matthew
  3. Want to Get in Touch? @webdevwithmatt https: // webdevwithmatt.com

  4. What Will You Learn?

  5. How to deploy PHP apps using Docker Compose Picture: Docker

    Compose logo courtesy of Unixmen / https: // www.unixmen.com/container-docker-compose-ubuntu-16-04/
  6. What Will You Learn About Docker? • Build and test

    a local development environment • Build a production environment from the development version • Build and tag self-contained Docker images • Push the Docker images to a Docker image registry • Test the production deployment • Deploy to production
  7. source: https://docs.docker.com/get-started/overview/#docker-architecture “Docker uses a client-server architecture. The Docker client

    talks to the Docker daemon, which does the heavy lifting of building, running, and distributing your Docker containers. The Docker client and daemon can run on the same system, or you can connect a Docker client to a remote Docker daemon. The Docker client and daemon communicate using a REST API over UNIX sockets or a network interface.” The Docker Architecture
  8. Can Be Easier Than... Picture: Docker Swarm logo courtesy of

    Sum Global / https: // sumglobal.com/wp-content/uploads/2017/07/docker-swarm-logo.png Picture: Kubernetes logo courtesy of wikimedia.org / https: // commons.wikimedia.org/wiki/File:Kubernetes_logo.svg
  9. Why Deploy With Docker?

  10. Why Deploy With Docker? • Docker can be a simpler

    to design and deploy with • Use a core set of standard commands • Can deploy to production almost as easily as to development • Can easily share setups with other developers • Can readily integrate with CI/CD tools
  11. What Do You Need?

  12. What Do You Need? • A broad familiarity with Docker

    • Docker Engine on Linux • Docker Desktop on macOS and Microsoft Windows • Docker Compose V2 • Access to a Docker Image Registry • A deployment server running Docker
  13. The Demo PHP Application

  14. None
  15. The Application’s Composition PHP Webserver Database

  16. “One process or service per container” source: https://docs.docker.com/config/containers/multi-service_container/

  17. Create the Development Configuration

  18. docker ├── development │ └── nginx │ ├── default.conf |

    └── Dockerfile | └── php | └── Dockerfile ├── production │ ├── database │ │ ├── Dockerfile │ │ └── dump.sql | ├── nginx | │ ├── Dockerfile | │ └── default.conf | └── php | └── Dockerfile ├── docker-compose.prod.yml └── docker-compose.dev.yml Create the Directory Structure
  19. docker ├── development │ └── nginx │ ├── default.conf |

    └── Dockerfile | └── php | └── Dockerfile ├── production │ ├── database │ │ ├── Dockerfile │ │ └── dump.sql | ├── nginx | │ ├── Dockerfile | │ └── default.conf | └── php | └── Dockerfile ├── docker-compose.prod.yml └── docker-compose.dev.yml Create the Directory Structure
  20. docker ├── development │ └── nginx │ ├── default.conf |

    └── Dockerfile | └── php | └── Dockerfile ├── production │ ├── database │ │ ├── Dockerfile │ │ └── dump.sql | ├── nginx | │ ├── Dockerfile | │ └── default.conf | └── php | └── Dockerfile ├── docker-compose.prod.yml └── docker-compose.dev.yml Create the Directory Structure
  21. docker ├── development │ └── nginx │ ├── default.conf |

    └── Dockerfile | └── php | └── Dockerfile ├── production │ ├── database │ │ ├── Dockerfile │ │ └── dump.sql | ├── nginx | │ ├── Dockerfile | │ └── default.conf | └── php | └── Dockerfile ├── docker-compose.prod.yml └── docker-compose.dev.yml Create the Directory Structure
  22. docker ├── development │ └── nginx │ ├── default.conf |

    └── Dockerfile | └── php | └── Dockerfile ├── production │ ├── database │ │ ├── Dockerfile │ │ └── dump.sql | ├── nginx | │ ├── Dockerfile | │ └── default.conf | └── php | └── Dockerfile ├── docker-compose.prod.yml └── docker-compose.dev.yml Create the Directory Structure
  23. Create the Dockerfiles

  24. FROM nginx:alpine COPY ./docker/production/nginx/default.conf \ /etc/nginx/conf.d/default.conf The NGINX Dockerfile

  25. FROM nginx:alpine COPY ./docker/production/nginx/default.conf \ /etc/nginx/conf.d/default.conf The NGINX Dockerfile

  26. I 😍 Alpine Linux Picture: Alpine Linux logo courtesy of

    techcentral.ie / http: // www.techcentral.ie/wp-content/uploads/2017/02/Alpine_Linux_logo_web.jpg
  27. • Smaller than other distress • Around 5MB in size

    • Reduces image hosting costs • Reduces image transfer costs • Reduces deployment time • Is a Docker best practice I 😍 Alpine Linux Picture: Alpine Linux logo courtesy of techcentral.ie / http: // www.techcentral.ie/wp-content/uploads/2017/02/Alpine_Linux_logo_web.jpg
  28. FROM nginx:alpine COPY ./docker/production/nginx/default.conf \ /etc/nginx/conf.d/default.conf The NGINX Dockerfile

  29. server { listen 80 default_server; root /var/www/html/public; index index.php; access_log

    /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { try_files $uri $uri/ /index.php; } location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass php:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param APPLICATION_ENV development; fastcgi_intercept_errors off; fastcgi_buffer_size 16k; fastcgi_buffers 4 16k; } }
  30. FROM php:7.4-fpm-alpine3.13 RUN docker-php-ext-install pdo_mysql The PHP Dockerfile

  31. FROM php:7.4-fpm-alpine3.13 RUN docker-php-ext-install pdo_mysql The PHP Dockerfile

  32. FROM php:7.4-fpm-alpine3.13 RUN docker-php-ext-install pdo_mysql The PHP Dockerfile

  33. FROM mariadb:latest COPY ./docker/production/database/dump.sql \ /docker-entrypoint-initdb.d/dump.sql The Database Dockerfile

  34. FROM mariadb:latest COPY ./docker/production/database/dump.sql \ /docker-entrypoint-initdb.d/dump.sql The Database Dockerfile

  35. DROP TABLE IF EXISTS records; CREATE TABLE records ( id

    int unsigned NOT NULL, first_name varchar(200) NOT NULL, last_name varchar(200) NOT NULL, email_address varchar(255) NOT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; dump.sql
  36. LOCK TABLES records WRITE; INSERT INTO records VALUES (2,'Steve','McGarrett','steve.mcgarrett@hawaii-50.org'), (3,'Danny','Williams','danny.williams@hawaii-50.org'),

    (4,'Kamekona','','kamekona@hawaii-50.org'),(5,'Chin Ho','Kelly','chin.ho.kelly@hawaii-50.org'), (6,'Kono','Kalakaua','kono.kalakaua@hawaii-50.org'), (7,'Lou','Grover','lou.grover@hawaii-50.org'), (8,'Duke','Lukela','duke.lukela@hawaii-50.org'), (9,'Max','Bergman','max.bergman@hawaii-50.org'), (10,'Jerry','Ortega','jerry.ortega@hawaii-50.org'), (11,'Adam','Noshimuri','adam.noshimuri@hawaii-50.org'); UNLOCK TABLES; dump.sql
  37. The Docker Compose Configuration

  38. volumes: database_data: driver: local A Persistable Volume

  39. services: nginx: build: ./docker/development/nginx/ restart: always ports: - 8080:80 volumes:

    - ./:/var/www/html healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The NGINX Service Configuration
  40. services: nginx: build: ./docker/development/nginx/ restart: always ports: - 8080:80 volumes:

    - ./:/var/www/html healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The NGINX Service Configuration
  41. services: nginx: build: ./docker/development/nginx/ restart: always ports: - 8080:80 volumes:

    - ./:/var/www/html healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The NGINX Service Configuration
  42. services: nginx: build: ./docker/development/nginx/ restart: always ports: - 8080:80 volumes:

    - ./:/var/www/html healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The NGINX Service Configuration
  43. The restart Directive’s Options Setting Description no This is the

    default value. When set, Docker does not restart a container for any reason. on-failure When set, Docker restarts a container if the exit code indicates an on-failure error. unless-stopped When set, Docker always restarts a container, except when the container is stopped (manually or otherwise).
  44. services: nginx: build: ./docker/development/nginx/ restart: always ports: - 8080:80 volumes:

    - ./:/var/www/html healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The NGINX Service Configuration
  45. services: nginx: build: ./docker/development/nginx/ restart: always ports: - 8080:80 volumes:

    - ./:/var/www/html healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The NGINX Service Configuration
  46. services: nginx: build: ./docker/development/nginx/ restart: always ports: - 8080:80 volumes:

    - ./:/var/www/html healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The NGINX Service Configuration
  47. php: build: ./docker/development/php/ restart: always volumes: - .:/var/www/html The PHP

    Service Configuration
  48. php: build: ./docker/development/php/ restart: always volumes: - .:/var/www/html The PHP

    Service Configuration
  49. php: build: ./docker/development/php/ restart: always volumes: - .:/var/www/html The PHP

    Service Configuration
  50. php: build: ./docker/development/php/ restart: always volumes: - .:/var/www/html The PHP

    Service Configuration
  51. php: build: ./docker/development/php/ restart: always volumes: - .:/var/www/html The PHP

    Service Configuration
  52. database: build: ./docker/development/database/ restart: always ports: - 3306:3306 environment: MARIADB_DATABASE:

    hawaii-five-0 MARIADB_USER: user MARIADB_PASSWORD: password volumes: - ./data/database:/var/lib/mysql/ The Database Service Configuration
  53. database: build: ./docker/development/database/ restart: always ports: - 3306:3306 environment: MARIADB_DATABASE:

    hawaii-five-0 MARIADB_USER: user MARIADB_PASSWORD: password volumes: - ./data/database:/var/lib/mysql/ The Database Service Configuration
  54. database: build: ./docker/development/database/ restart: always ports: - 3306:3306 environment: MARIADB_DATABASE:

    hawaii-five-0 MARIADB_USER: user MARIADB_PASSWORD: password volumes: - ./data/database:/var/lib/mysql/ The Database Service Configuration
  55. database: build: ./docker/development/database/ restart: always ports: - 3306:3306 environment: MARIADB_DATABASE:

    hawaii-five-0 MARIADB_USER: user MARIADB_PASSWORD: password volumes: - ./data/database:/var/lib/mysql/ The Database Service Configuration
  56. database: build: ./docker/development/database/ restart: always ports: - 3306:3306 environment: MARIADB_DATABASE:

    hawaii-five-0 MARIADB_USER: user MARIADB_PASSWORD: password volumes: - ./data/database:/var/lib/mysql/ The Database Service Configuration
  57. Deploy the Application Locally

  58. docker compose \ 
 -- file docker-compose.dev.yml up \ 


    -- build Deploy the Application Locally
  59. [+] Running 4/4 ⠿ Network docker-essentials-test-build_default Created 0.1s ⠿ Container

    docker-essentials-test-build_php_1 Created 0.2s ⠿ Container docker-essentials-test-build_nginx_1 Created 0.3s ⠿ Container docker-essentials-test-build_database_1 Created 1.2s Attaching to database_1, nginx_1, php_1 php_1 | [18-Nov-2021 13:19:29] NOTICE: fpm is running, pid 1 php_1 | [18-Nov-2021 13:19:29] NOTICE: ready to handle connections nginx_1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ nginx_1 | 2021/11/18 13:19:30 [notice] 1#1: using the "epoll" event method nginx_1 | 2021/11/18 13:19:30 [notice] 1#1: nginx/1.21.3 nginx_1 | 2021/11/18 13:19:30 [notice] 1#1: built by gcc 8.3.0 (Debian 8.3.0-6) nginx_1 | 2021/11/18 13:19:30 [notice] 1#1: OS: Linux 5.10.47-linuxkit nginx_1 | 2021/11/18 13:19:30 [notice] 1#1: start worker processes nginx_1 | 2021/11/18 13:19:30 [notice] 1#1: start worker process 32 nginx_1 | 2021/11/18 13:19:30 [notice] 1#1: start worker process 33 Docker Compose Up Output
  60. docker compose \ 
 -- file docker-compose.dev.yml up \ 


    —build -d Deploy the Application Locally
  61. docker pull nginx:alpine docker pull php:7.4-fpm-alpine3.13 docker pull mariadb:latest Save

    Some Time With docker pull
  62. Are the Containers Working as Expected?

  63. docker compose ps docker compose -- file docker-compose.dev.yml ps NAME

    COMMAND SERVICE STATUS PORTS docker-essentials_database_1 "docker-entrypoint.s…" database running 0.0.0.0:3306 -> 3306/tcp, 33060/tcp docker-essentials_nginx_1 "/docker-entrypoint.…" nginx running 0.0.0.0:8080 -> 80/tcp, docker-essentials_php_1 "docker-php-entrypoi…" php running 9000/tcp
  64. docker compose ps docker compose -- file docker-compose.dev.yml ps database

    NAME COMMAND SERVICE STATUS PORTS docker-essentials_database_1 "docker-entrypoint.s…" database running 0.0.0.0:3306 -> 3306/tcp, 33060/tcp
  65. docker compose logs docker compose \ -- file docker-compose.dev.yml logs

    \ -- follow database
  66. The Local Deployment Works

  67. Quick Recap • Created an intuitive directory structure • Created

    Dockerfiles & supporting files for each service • Created a Docker Compose configuration • Deployed the application locally • Did basic testing and debugging
  68. Create the Production Configuration

  69. Create the Production Dockerfiles

  70. FROM nginx:alpine WORKDIR /var/www/html COPY . . COPY ./docker/nginx/default.prod.conf /etc/nginx/conf.d/default.conf

    RUN apk add -- update nodejs npm \ && npm install RUN NODE_ENV=production npx tailwindcss \ -i src/css/styles.css \ -o public/css/styles.css \ -- minify The NGINX Production Dockerfile
  71. FROM nginx:alpine WORKDIR /var/www/html COPY . . COPY ./docker/nginx/default.prod.conf /etc/nginx/conf.d/default.conf

    RUN apk add -- update nodejs npm \ && npm install RUN NODE_ENV=production npx tailwindcss \ -i src/css/styles.css \ -o public/css/styles.css \ -- minify The NGINX Production Dockerfile
  72. FROM nginx:alpine WORKDIR /var/www/html COPY ./docker/nginx/default.prod.conf /etc/nginx/conf.d/default.conf COPY . .

    RUN apk add -- update nodejs npm \ && npm install RUN NODE_ENV=production npx tailwindcss \ -i src/css/styles.css \ -o public/css/styles.css \ -- minify The NGINX Production Dockerfile
  73. FROM nginx:alpine WORKDIR /var/www/html COPY . . COPY ./docker/nginx/default.prod.conf /etc/nginx/conf.d/default.conf

    RUN apk add -- update nodejs npm \ && npm install RUN NODE_ENV=production npx tailwindcss \ -i src/css/styles.css \ -o public/css/styles.css \ -- minify The NGINX Production Dockerfile
  74. FROM nginx:alpine WORKDIR /var/www/html COPY . . COPY ./docker/nginx/default.prod.conf /etc/nginx/conf.d/default.conf

    RUN apk add -- update nodejs npm \ && npm install RUN NODE_ENV=production npx tailwindcss \ -i src/css/styles.css \ -o public/css/styles.css \ -- minify The NGINX Production Dockerfile
  75. FROM php:7.4-fpm-alpine3.13 as composer RUN apk add - - update

    git RUN curl -- silent - - fail -- location -- retry 3 -- output /tmp/installer.php -- url https: / / raw.githubusercontent.com/composer/getcomposer.org/cb19f2aa3aeaa2006c0cd69a7ef011eb31463067/web/ installer \ && php -r " \ \$signature = '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5'; \ \$hash = hash('sha384', file_get_contents('/tmp/installer.php')); \ if (!hash_equals(\$signature, \$hash)) { \ unlink('/tmp/installer.php'); \ echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \ exit(1); \ }" \ && php /tmp/installer.php -- no-ansi - - install-dir=/usr/bin -- filename=composer \ && composer -- ansi -- version -- no-interaction \ && rm -f /tmp/installer.php The PHP Production Dockerfile
  76. FROM php:7.4-fpm-alpine3.13 as composer RUN apk add - - update

    git RUN curl -- silent - - fail -- location -- retry 3 -- output /tmp/installer.php -- url https: / / raw.githubusercontent.com/composer/getcomposer.org/cb19f2aa3aeaa2006c0cd69a7ef011eb31463067/web/ installer \ && php -r " \ \$signature = '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5'; \ \$hash = hash('sha384', file_get_contents('/tmp/installer.php')); \ if (!hash_equals(\$signature, \$hash)) { \ unlink('/tmp/installer.php'); \ echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \ exit(1); \ }" \ && php /tmp/installer.php -- no-ansi - - install-dir=/usr/bin -- filename=composer \ && composer -- ansi -- version -- no-interaction \ && rm -f /tmp/installer.php The PHP Production Dockerfile
  77. “With multi-stage builds, you use multiple FROM statements in your

    Dockerfile.” source: https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
  78. The PHP Production Dockerfile FROM php:7.4-fpm-alpine3.13 as composer RUN apk

    add - - update git RUN curl -- silent - - fail -- location -- retry 3 -- output /tmp/installer.php -- url https: / / raw.githubusercontent.com/composer/getcomposer.org/cb19f2aa3aeaa2006c0cd69a7ef011eb31463067/web/ installer \ && php -r " \ \$signature = '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5'; \ \$hash = hash('sha384', file_get_contents('/tmp/installer.php')); \ if (!hash_equals(\$signature, \$hash)) { \ unlink('/tmp/installer.php'); \ echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \ exit(1); \ }" \ && php /tmp/installer.php -- no-ansi - - install-dir=/usr/bin -- filename=composer \ && composer -- ansi -- version -- no-interaction \ && rm -f /tmp/installer.php
  79. The PHP Production Dockerfile FROM php:7.4-fpm-alpine3.13 as composer RUN apk

    add - - update git RUN curl -- silent - - fail -- location -- retry 3 -- output /tmp/installer.php -- url https: / / raw.githubusercontent.com/composer/getcomposer.org/cb19f2aa3aeaa2006c0cd69a7ef011eb31463067/web/ installer \ && php -r " \ \$signature = '48e3236262b34d30969dca3c37281b3b4bbe3221bda826ac6a9a62d6444cdb0dcd0615698a5cbe587c3f0fe57a54d8f5'; \ \$hash = hash('sha384', file_get_contents('/tmp/installer.php')); \ if (!hash_equals(\$signature, \$hash)) { \ unlink('/tmp/installer.php'); \ echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \ exit(1); \ }" \ && php /tmp/installer.php -- no-ansi - - install-dir=/usr/bin -- filename=composer \ && composer -- ansi -- version -- no-interaction \ && rm -f /tmp/installer.php
  80. FROM php:7.4-fpm-alpine3.13 as dependencies WORKDIR /var/www/html COPY . . COPY

    -- from=composer /usr/bin/composer /usr/bin/ RUN /usr/bin/composer install \ -- no-dev - - no-ansi -- no-plugins -- no-progress -- no-scripts \ -- classmap-authoritative -- no-interaction \ -- quiet RUN docker-php-ext-install pdo_mysql RUN rm -f config/autoload/development.local.php config/development.config.php The PHP Production Dockerfile
  81. FROM php:7.4-fpm-alpine3.13 as dependencies WORKDIR /var/www/html COPY . . COPY

    -- from=composer /usr/bin/composer /usr/bin/ RUN /usr/bin/composer install \ -- no-dev - - no-ansi -- no-plugins -- no-progress -- no-scripts \ -- classmap-authoritative -- no-interaction \ -- quiet RUN docker-php-ext-install pdo_mysql RUN rm -f config/autoload/development.local.php config/development.config.php The PHP Production Dockerfile
  82. FROM php:7.4-fpm-alpine3.13 as dependencies WORKDIR /var/www/html COPY . . COPY

    -- from=composer /usr/bin/composer /usr/bin/ RUN /usr/bin/composer install \ -- no-dev - - no-ansi -- no-plugins -- no-progress -- no-scripts \ -- classmap-authoritative -- no-interaction \ -- quiet RUN docker-php-ext-install pdo_mysql RUN rm -f config/autoload/development.local.php config/development.config.php The PHP Production Dockerfile
  83. FROM php:7.4-fpm-alpine3.13 as dependencies WORKDIR /var/www/html COPY . . COPY

    -- from=composer /usr/bin/composer /usr/bin/ RUN /usr/bin/composer install \ -- no-dev - - no-ansi -- no-plugins -- no-progress -- no-scripts \ -- classmap-authoritative -- no-interaction \ -- quiet RUN docker-php-ext-install pdo_mysql RUN rm -f config/autoload/development.local.php config/development.config.php The PHP Production Dockerfile
  84. FROM mariadb:latest COPY ./docker/production/database/dump.sql \ /docker-entrypoint-initdb.d/dump.sql The Database Dockerfile

  85. Build the Docker Images

  86. docker build \ -- file ./docker/production/nginx/Dockerfile \ -- tag webdevwithmatt/webserver-alpine:latest

    . docker build \ -- file ./docker/production/php/Dockerfile \ -- tag webdevwithmatt/php-runtime-alpine:latest . docker build \ -- file ./docker/production/database/Dockerfile \ -- tag webdevwithmatt/database-alpine:latest . Build the Docker Images
  87. docker build \ -- file ./docker/production/php/Dockerfile \ -- tag webdevwithmatt/php-runtime-alpine:latest

    \ . Build the Docker Images
  88. docker build \ -- file ./docker/production/php/Dockerfile \ -- tag webdevwithmatt/php-runtime-alpine:latest

    \ . Build the Docker Images
  89. Check the Docker Images

  90. $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE webdevwithmatt/webserver-alpine

    latest 8c6f620e0709 9 seconds ago 432MB webdevwithmatt/php-runtime-alpine latest 6544148b5c30 9 seconds ago 495MB webdevwithmatt/database-alpine latest 6544148b5c30 9 seconds ago 495MB Check the Docker Images
  91. docker images -- filter=reference='registry*/*/*:latest' Filter Docker Images

  92. Push the Docker Images

  93. docker login - - username <username> -- password <password> docker

    push webdevwithmatt/webserver-alpine:latest docker push webdevwithmatt/php-runtime-alpine:latest docker push webdevwithmatt/database-alpine:latest Push the Docker Images
  94. Create the Production Docker Compose Configuration

  95. volumes: database_data: driver: local composer_dependencies: The Production Docker Compose File

  96. services: nginx: image: webdevwithmatt/webserver-alpine:latest restart: always volumes: - composer_dependencies:/var/www/html/vendor ports:

    - 80:80 depends_on: - php healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The Production Docker Compose File
  97. services: nginx: image: webdevwithmatt/webserver-alpine:latest restart: always volumes: - composer_dependencies:/var/www/html/vendor ports:

    - 80:80 depends_on: - php healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The Production Docker Compose File
  98. services: nginx: image: webdevwithmatt/webserver-alpine:latest restart: always volumes: - composer_dependencies:/var/www/html/vendor ports:

    - 80:80 depends_on: - php healthcheck: test: ["CMD", "curl", "-f", "http: // localhost:8000/api/ping"] interval: 60s timeout: 3s retries: 3 The Production Docker Compose File
  99. php: image: webdevwithmatt/php-runtime-alpine:latest restart: always expose: - 9000 volumes: -

    composer_dependencies:/var/www/html/vendor The Production Docker Compose File
  100. database: image: webdevwithmatt/database-alpine:latest restart: always ports: - 3306:3306 environment: -

    MARIADB_ROOT_PASSWORD - MARIADB_DATABASE - MARIADB_USER - MARIADB_PASSWORD The Production Docker Compose File
  101. database: image: webdevwithmatt/database-alpine:latest restart: always ports: - 3306:3306 environment: -

    MARIADB_DATABASE - MARIADB_USER - MARIADB_PASSWORD The Production Docker Compose File
  102. Set Environment Variables

  103. export MARIADB_DATABASE=project_db export MARIADB_USER=user export MARIADB_PASSWORD=password Set Environment Variables

  104. Test the Deployment

  105. docker compose -- file docker-compose.prod.yml up -d Test the Deployment

  106. docker compose -- file docker-compose.prod.yml ps Test the Deployment

  107. docker compose -- file docker-compose.prod.yml down Test the Deployment

  108. Deploy to Production

  109. Deploy to Production • Set DOCKER_HOST • Use -H or

    --host options with Docker commands • Use a Docker Context
  110. Deploy to Production DOCKER_HOST=ssh: / / webdevwithmatt@<prod host> docker compose

    up export DOCKER_HOST=ssh: // webdevwithmatt@<prod host> && \ docker compose up docker -H ssh: // webdevwithmatt@<prod host> compose up docker - - host ssh: / / webdevwithmatt@<prod host> compose up
  111. source: https://docs.docker.com/engine/context/working-with-contexts/ A single Docker CLI can have multiple contexts.

    Each context contains all of the endpoint and security information required to manage a different cluster or node. The docker context command makes it easy to configure these contexts and switch between them.
  112. Docker Contexts • Create a context • Set it as

    the default context • Run Docker commands
  113. Docker Contexts docker context create remote \ —docker "host=ssh: /

    / deploy@<your_digitalocean_droplets_ip>" docker context use remote
  114. docker context ls NAME TYPE DESCRIPTION DOCKER ENDPOINT KUBERNETES ENDPOINT

    ORCHESTRATOR default moby Current DOCKER_HOST based configuration unix: // / var/run/docker.sock https: // localhost:6443 (default) swarm remote * moby ssh: // webdevwithmatt@<prod host> Verify the Context
  115. Deploy to Production export DOCKER_HOST=ssh: / / webdevwithmatt@<prod host> docker

    compose -f docker-compose.prod.yml up -d
  116. Did It Work?

  117. docker compose docker-compose.prod.yml ps Check That it Worked

  118. Final Recap

  119. Final Recap • How to build the application image •

    How to store the image in an accessible registry • How to tell the Docker client which daemon to communicate with • How to build a deployment configuration • How to deploy the application to a remote host
  120. What About a Build Pipeline?

  121. What About a Build Pipeline? Picture: Github Actions courtesy of

    dev.to / https: // res.cloudinary.com/practicaldev/image/fetch/s -- vBB9KCW4 -- /c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https: // dev-to-uploads.s3.amazonaws.com/i/twfkvzk83m0cpjwz26js.png 
 Picture: circleci logo courtesy of freelogovectors.net / https: // cdn.freelogovectors.net/wp-content/uploads/2020/11/circle-ci-logo.png Picture: TeamCity logo courtesy of freebiesupply.com / https: // cdn.freebiesupply.com/logos/large/2x/teamcity-icon-logo-png-transparent.png
  122. Want to Dive Deeper? https: // dockeressentials.com

  123. Want to Dive Deeper? • It goes into greater depth

    • It's packed with tips and tricks • It's free! https: // dockeressentials.com
  124. Thank You

  125. Questions?