Skip to content

Instantly share code, notes, and snippets.

@pandorasNox
Last active April 12, 2021 12:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pandorasNox/56ed7d0aa521df4eb1c7972b890124a3 to your computer and use it in GitHub Desktop.
Save pandorasNox/56ed7d0aa521df4eb1c7972b890124a3 to your computer and use it in GitHub Desktop.
workshop docker / docker-compose

docker-compose

  • everything you learn you actually already know

    • it's absolutly the same as with just docker
    • only difference is, it's more declerative
  • let's learn and compare pure docker vs docker-compose

agenda

  • repeat docker basics

  • docker-compose

    • docker-compose.yaml in detail
    • docker vs docker-compose(.yaml)
  • issue on mac

    • if you run into docker.credentials.errors.StoreError: Credentials store docker-credential-desktop exited with "No stored credential for https://index.docker.io/v1/".
    • try
    • in cat ~/.docker/config.json
    • set "credsStore" : ""

docker-compose

why

  • example: makefile only docker vs docker-compose
    • => docker-compose is much simpler
    • auto naming
    • easy cleanup
    • easy networking
    • declarative

create & validate yaml

  • val:
     mkdir -p /tmp/workshops/docker/
     cd /tmp/workshops/docker/
    
     # create yaml
     touch docker-compose.yaml
    
     # validate
     docker-compose config
    
     # => should fail with smth like:
     # $
     # `ERROR: Top level object in './docker-compose.yaml' needs to be an object not '<class 'NoneType'>'.`
    
     # make docker-compose.yaml valid
     echo 'version: "3.8"' > docker-compose.yaml
    
     # validate again
     docker-compose config
    
     # works also, but not recommendet:
     echo '{}' > docker-compose.yaml
     docker-compose config
    
     # => should return:
     # $
     # services: {}
     # version: '1'

docker vs docker-compose(.yaml)

let's compare docker and docker-compose

# docker pull nginx:1.18.0

basic setup

cd /tmp
mkdir -p /tmp/workshops/docker

#for example XYZ
mkdir -p /tmp/workshops/docker/example01

cd /tmp/workshops/docker/example01

touch docker-compose.yaml

nano docker-compose.yaml

# ...
  • pure docker example cleanups (for every example) cleanup:
docker ps
# grap container id, e.g. `35435345345`
docker stop 35435345345
docker rm 35435345345

example1 - simple single nginx container

  • step 0 - setup

    • do pure docker cleanup
  • step 1 - docker recap docker:

     docker run -d nginx:1.18.0-alpine
  • step 2 - docker-compose (new / learn)

    • docker-compose.yaml:
       version: "3.8"
      
       services:
         my-nginx-svc:
       	image: "nginx:1.18.0-alpine"
    • run it - docker-compose up:
       docker-compose up -d
  • step 3 - cleanup

    • todo

example2 - simple single nginx container - with port

  • step 0 - info / setup

  • step 1 - docker recap

    • with docker:

       docker run -d -p 10080:80 nginx:1.18.0-alpine
    • again the left port is the host port, the right port is the container port

      • which is also the port listened on from nginx (@see ...)
    • check:

       curl localhost:8080
       #or
       curl 127.0.0.1:8080
  • setp 2 - docker-compose (new / learn)

    • setup

      • docker-compose.yaml:
         version: "3.8"
        
         services:
           my-nginx-svc:
         	image: "nginx:1.18.0-alpine"
         	ports:
         	  - "10080:80"
    • cmd's

      • docker-compose up
         docker-compose up -d
    • checking

       docker ps
       # vs
       docker-compose ps
      
       # return smth like
       #
       # ... `example02_my-nginx-svc_1` <= name
    • cleanup tear down:

       docker-compose down
      

example_03 - pure php container

  • step 0 - info / setup

    • info
      • container with just php
      • we only can run a php script
      • container will stop after script finsihes
      • webserver example is next up
    • setup
      • dir setup:

         cd /tmp
         mkdir -p /tmp/workshops/docker
        
         mkdir -p /tmp/workshops/docker/example_03
        
         cd /tmp/workshops/docker/example_03
        
         touch index.php
         touch Dockerfile
         touch docker-compose.yaml
      • index.php:

         <?php
        
         echo "Hello World, from php script";
        
        
      • Dockerfile:

         FROM php:7.4.16-cli-alpine3.13
        
         COPY index.php /
        
         # note:
         # the entrypoint from the `php:7.4.16-cli-alpine3.13` image (looked up on `https://hub.docker.com/_/php`) looks like:
         #
         # ENTRYPOINT ["docker-php-entrypoint"]
         # CMD ["php", "-a"]
        
         # note:
         # - we have to overwrite the entrypoint or CMD, to point to our script
         # (which we copied into the container image)
         #
         # ENTRYPOINT => the cmd which will be executed when the container starts
         # CMD => arguments passed to the entrypoint
         #
         # the naming is historical bad
         #
         ENTRYPOINT ["docker-php-entrypoint", "php", "/index.php"]
        
         # note:
         # use CMD only to define _DEFAULT_ arguments for your actual command
         # see: https://stackoverflow.com/a/60359815 fore more info
         #
         # example:
         #
         # given entrypoint looks like:
         # ENTRYPOINT ["docker-php-entrypoint", "php"]
         # (note we didn't add  the `php` executable here):
         # CMD ["/index.php"]
        
        
      • Dockerfile (no comments):

         FROM php:7.4.16-cli-alpine3.13
        
         COPY index.php /
        
         ENTRYPOINT ["docker-php-entrypoint", "php", "/index.php"]
        
  • step 1 - docker recap

    • docker:
       # build + tag image
       docker build -t dw-php-cli-example .
      
       # run
       docker run dw-php-cli-example
      
       # check
       docker ps -a
      
       #
       #
       # expecting smth like:
       # 98417012f685   dw-php-cli-example   "docker-php-entrypoi…"   7 seconds ago   Exited (0) 5 seconds ago             inspiring_swartz
      
       # clean up
       docker rm <container-id>
      
       # with auto-remove
       docker run --rm dw-php-cli-example
      
  • setp 2 - docker-compose (new / learn)

    • todo

example_XX - apache (only shown to participants, not played through)

  • step 0 - setup Dockerfile:

     FROM httpd:2.4.46
     COPY ./public-html/ /usr/local/apache2/htdocs/
    
  • step 1 - docker recap docker:

     docker run -dit --name my-running-app -p 8080:80 my-apache2
    
     #or
    
     docker run -d --rm -p 10080:80 httpd:2.4.46
    

example_04 - php + webserver (apache httpd, skipping nginx setup bec. of time)

  • step 0 - setup
    • index.php:
       <?php
      
       echo "Hello World, from webserver";
      
      
    • Dockerfile:
       FROM php:7.4.16-apache-buster
       COPY index.php /var/www/html/index.php
      
  • step 1 - docker recap
    • build:
       docker build -t dw-php-apach-example .
      
    • run:
       docker run -d --rm -p 10080:80 dw-php-apach-example
      
    • check:
      curl localhost:10080
      
      or check via browser
    • cleanup ...
  • setp 2 - docker-compose (new / learn)
    • docker-compose.yaml
     version: "3.8"
    
     services:
       my-php-apache:
     	build:
     	  context: "."
     	  dockerfile: Dockerfile
     	ports:
     	  - "10080:80"
    • build
     docker-compose build
     #or
     docker-compose build my-php-apache
    
    • run
     docker-compose up -d
    
    • check
     docker-compose ps
     curl localhost:10080
    
    • clean up
     docker-compose down -t 1
    
    • check docker ps -a

example_05 - php + webserver (apache httpd) + postgres

  • step 0 - setup

    • index.php:
       <?php
       declare(strict_types = 1);
      
       $pgHost = $_ENV["MYAPP_POSTGRES_HOST"] ?? 'localhost';
       echo $pgHost;
      • side note
         #$foo = "" ?? "bar"; # => ""
         #vs
         #$foo = "" :? "bar"; # => "bar"
        
    • index.php:
       <?php
      
       declare(strict_types = 1);
      
       # plan of attack:
       # - handle env vars proper
       # - get current timestamp
       # - try to create table if not existing
       # - try to save new time stamp
       # - look up saved time stamps
       # - on success display last 100 timestamps
      
       # check env vars which are not ment to have a default
       # and FAIL obviously and have good cli output to point user in right direction
       if (!function_exists('returnSystemEnv')) {
       	function returnSystemEnv($envKey, $fallback = false)
       	{
       		$value = getenv($envKey);
       		if ($value === false && $fallback === false) {
       			throw new Error('value for environment variable "' . $envKey . '" is missing');
       		}
       		if ($value === false && $fallback !== false) {
       			return $fallback;
       		}
       		return $value;
       	}
       }
      
       # handle env vars
       $pgHost = returnSystemEnv('MYAPP_POSTGRES_HOST');
       $pgPort = returnSystemEnv(
       	'MYAPP_POSTGRES_PORT',  #env key
       	'5432',                 #fallback value
       );
       $pgUser = returnSystemEnv("MYAPP_POSTGRES_USER");
       $pgUserPass = returnSystemEnv("MYAPP_POSTGRES_USER_PASSWORD");
       $pgDbName = returnSystemEnv("MYAPP_POSTGRES_DB_NAME");
      
       $pdoString = "pgsql:host=${pgHost}; port=${pgPort}; user=${pgUser}; password=${pgUserPass}; dbname=${pgDbName};";
       #$pdoString = sprintf('pgsql:host=%s; port=%s; ', $pgHost, $pgPort)
      
       $currentTimestamp = date_timestamp_get(date_create());
      
       $table = 'timestamps_for_visits';
      
       $sqlCreateIfNotExist = "CREATE TABLE IF NOT EXISTS $table (";
       $sqlCreateIfNotExist .= "timestamp_id SERIAL,";
       $sqlCreateIfNotExist .= "timestamp varchar(255) NOT NULL";
       $sqlCreateIfNotExist .= ");";
      
       $sqlInsertTimestamp = "INSERT INTO $table (timestamp) VALUES ($currentTimestamp);";
      
       try {
       	$pdo = new PDO($pdoString);
       	// if($pdo->exec($sql) !== false) { return 1; }
       	$resCreateIf = $pdo->exec($sqlCreateIfNotExist);
       	// var_dump($resCreateIf);
      
       	$resInsert = $pdo->exec($sqlInsertTimestamp);
       	// var_dump($resCreateIf);
      
       	$stmt = $pdo->query("SELECT timestamp FROM $table ORDER BY timestamp_id DESC LIMIT 100");
      
       	while ($row = $stmt->fetch()) {
       		echo $row['timestamp']."<br />\n";
       	}
       } catch (Throwable $e) {
       	echo 'Very nice way to catch Exception and Error exceptions';
       	echo "\n";
       	echo $e;
       	echo "\n";
      
       	die("Could not connect");
       }
    • docker
       docker pull postgres:9.6.21-alpine
       docker pull adminer
    • Dockerfile:
       FROM php:7.4.16-apache-buster
      
       RUN apt-get update && apt-get install -y \
       	libpq-dev \
       	&& apt-get clean \
       	&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
      
       RUN docker-php-ext-install pdo pdo_pgsql pgsql
      
       COPY index.php /var/www/html/index.php
    • docker-compose.yaml
       version: "3.8"
      
       services:
      
         db:
       	image: postgres:9.6.21-alpine
       	restart: always
       	environment:
       	  POSTGRES_USER: root
       	  POSTGRES_PASSWORD: example
       	  POSTGRES_DB: mydb
      
         adminer:
       	image: adminer
       	restart: always
       	ports:
       	  - 18080:8080
      
         my-php-apache:
       	build:
       	  context: "."
       	  dockerfile: Dockerfile
       	ports:
       	  - "10080:80"
       	environment:
       	  MYAPP_POSTGRES_HOST: foo
       
  • step 1 - docker recap

    • build:
       docker build -t dw-php-apach-example .
    • run:
       docker run -d --rm -p 10080:80 --name example_05_app \
       -e MYAPP_POSTGRES_HOST=db \
       -e MYAPP_POSTGRES_PORT=5432 \
       -e MYAPP_POSTGRES_USER=root \
       -e MYAPP_POSTGRES_USER_PASSWORD=example \
       -e MYAPP_POSTGRES_DB_NAME=mydb \
       dw-php-apach-example
      
       docker run -d --rm --name example_05_db \
       -e POSTGRES_USER=root \
       -e POSTGRES_PASSWORD=example \
       -e POSTGRES_DB=mydb \
       postgres:9.6.21-alpine
       
       docker run -d --rm -p 18080:8080 --name example_05_adminer \
       adminer
    • create & join network
       docker network create -d bridge example_05_net
       
       # docker network connect [OPTIONS] NETWORK CONTAINER
       docker network connect --alias="my-php-apache" example_05_net example_05_app
       docker network connect --alias="db" example_05_net example_05_db
       docker network connect --alias="adminer" example_05_net example_05_adminer
    • resetup.sh help script
      • setup
         touch resetup.sh
         chmod +x resetup.sh
      • content:
         #!/bin/bash
        
         set -euxo pipefail
        
         docker stop example_05_app 2> /dev/null || true
         docker stop example_05_db 2> /dev/null || true
         docker stop example_05_adminer 2> /dev/null || true
        
         docker build -t dw-php-apach-example .
        
         docker run -d --rm -p 10080:80 --name example_05_app \
         	-e MYAPP_POSTGRES_HOST=db \
         	-e MYAPP_POSTGRES_PORT=5432 \
         	-e MYAPP_POSTGRES_USER=root \
         	-e MYAPP_POSTGRES_USER_PASSWORD=example \
         	-e MYAPP_POSTGRES_DB_NAME=root_db \
         	dw-php-apach-example
        
         docker run -d --rm --name example_05_db \
         	-e POSTGRES_USER=root \
         	-e POSTGRES_PASSWORD=example \
         	-e POSTGRES_DB=root_db \
         	postgres:9.6.21-alpine
        
         docker run -d --rm -p 18080:8080 --name example_05_adminer \
         	adminer
        
         docker network connect --alias="my-php-apache" example_05_net example_05_app
         docker network connect --alias="db" example_05_net example_05_db
         docker network connect --alias="adminer" example_05_net example_05_adminer
        
    • check:
      curl localhost:10080
      
      or check via browser
    • cleanup
       docker stop example_05_db;
       docker stop example_05_app;
       docker network rm example_05_net;
       
       # maybe cleanup images ...
      

example_06 - php/laravel + webserver (apache httpd) + postgres

notes

  • static-site example with nginx
  • php application
  • php application with postgress
  • php laravel application setup (db included + migrations)
  • just running docker run creates always a new instance, while docker-compose doesn't
    • shoe docker create and docker start and dorkcer stop in detail vs docker run
  • accessing postgres via cli tools via docker exec
  • accessing postgress via external client after forwarding container port to host
  • running smth like a init job????
  • docker things we might need to talk about???:
    • ENTRYPOINT vs CMD
    • build context (often .)
    • using existing img (and cmd) vs building and using own image (on cmd + d-compose)
    • mount file/volume over file in container (over copied file in image)
  • a lot of image building is try and error
    • it's bec of the deps
    • you don't know what combination is going to work
    • os + software + libs + drivers + clients + ...
  • seperate CREATE db and app using db
    • havind smth like an init container / job / makefile script
    • app itself only uses db and fails iff no connection
  • env vars vs secrets (handling)
  • env vars namespaced more detailed example
    • when you do it vs when you don't
  • improvemt
    • last 100 timestamps as title
  • atendee info slides
    • after show single slides with infos they can use to build the example
    • show less new stuff with more complexe examlpe
  • git
    • after exercie use base line commit to bring everyone back on the same page

resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment