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" : ""
- if you run into
- example: makefile only docker vs docker-compose
- => docker-compose is much simpler
- auto naming
- easy cleanup
- easy networking
- declarative
- 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'
let's compare docker and docker-compose
# docker pull nginx:1.18.0
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
-
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
- docker-compose.yaml:
-
step 3 - cleanup
- todo
-
step 0 - info / setup
- info
- doc's: https://docs.docker.com/compose/compose-file/compose-file-v3/
- we want to be able to see our website
- we need to forward the http port, which the server listens on, so our browsers (local network) can reach it
- do pure docker cleanup
- info
-
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"
- docker-compose.yaml:
-
cmd's
- docker-compose up
docker-compose up -d
- docker-compose up
-
checking
docker ps # vs docker-compose ps # return smth like # # ... `example02_my-nginx-svc_1` <= name
-
cleanup tear down:
docker-compose down
-
-
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"]
-
- info
-
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
- docker:
-
setp 2 - docker-compose (new / learn)
- todo
-
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
- 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
- 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:
or check via browsercurl localhost:10080
- cleanup ...
- build:
- 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
-
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"
- side note
- 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
- index.php:
-
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
- setup
- check:
or check via browsercurl localhost:10080
- cleanup
docker stop example_05_db; docker stop example_05_app; docker network rm example_05_net; # maybe cleanup images ...
- build:
- 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
-
own knowledge
-
documentation
-
git repo
- structure
- cmds.sh
- info
- additional files
- e.g. Dockerfile, docker.compose.yaml ...
- idea
- examples consist of e.g. 5 commits
- latest commit for this example is tagged (for base line for the next exercise)
- every start commit contains info.md which contains infos like image and links to dockumentation
- this dockument grows with every commit, the newest infos always on top
- the cmds.sh script also grows with every commit
- ide is we could use the diff as slides e.g.
- structure