diff --git a/.github/workflows/pr-close-signal.yaml b/.github/workflows/pr-close-signal.yaml index 9b129d5..d20a299 100755 --- a/.github/workflows/pr-close-signal.yaml +++ b/.github/workflows/pr-close-signal.yaml @@ -16,7 +16,7 @@ jobs: mkdir -p ./pr printf ${{ github.event.number }} > ./pr/NUM - name: Upload Diff - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pr path: ./pr diff --git a/.github/workflows/pr-comment.yaml b/.github/workflows/pr-comment.yaml index bb2eb03..8a2bd3c 100755 --- a/.github/workflows/pr-comment.yaml +++ b/.github/workflows/pr-comment.yaml @@ -82,7 +82,7 @@ jobs: contents: write steps: - name: 'Checkout md outputs' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: md-outputs path: built diff --git a/.github/workflows/pr-receive.yaml b/.github/workflows/pr-receive.yaml index 371ef54..2d7d5db 100755 --- a/.github/workflows/pr-receive.yaml +++ b/.github/workflows/pr-receive.yaml @@ -25,7 +25,7 @@ jobs: - name: "Upload PR number" id: upload if: ${{ always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pr path: ${{ github.workspace }}/NR @@ -58,10 +58,10 @@ jobs: MD: ${{ github.workspace }}/site/built steps: - name: "Check Out Main Branch" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Check Out Staging Branch" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: md-outputs path: ${{ env.MD }} @@ -107,20 +107,20 @@ jobs: shell: Rscript {0} - name: "Upload PR" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pr path: ${{ env.PR }} - name: "Upload Diff" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: diff path: ${{ env.CHIVE }} retention-days: 1 - name: "Upload Build" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: built path: ${{ env.MD }} diff --git a/.github/workflows/sandpaper-main.yaml b/.github/workflows/sandpaper-main.yaml index e17707a..a4f8dc4 100755 --- a/.github/workflows/sandpaper-main.yaml +++ b/.github/workflows/sandpaper-main.yaml @@ -32,7 +32,7 @@ jobs: steps: - name: "Checkout Lesson" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Set up R" uses: r-lib/actions/setup-r@v2 diff --git a/.github/workflows/update-cache.yaml b/.github/workflows/update-cache.yaml index 676d742..08ea9c9 100755 --- a/.github/workflows/update-cache.yaml +++ b/.github/workflows/update-cache.yaml @@ -43,7 +43,7 @@ jobs: needed: ${{ steps.renv.outputs.exists }} steps: - name: "Checkout Lesson" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - id: renv run: | if [[ -d renv ]]; then @@ -76,7 +76,7 @@ jobs: steps: - name: "Checkout Lesson" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Set up R" uses: r-lib/actions/setup-r@v2 diff --git a/.github/workflows/update-workflows.yaml b/.github/workflows/update-workflows.yaml index 288bcd1..a2d6fee 100755 --- a/.github/workflows/update-workflows.yaml +++ b/.github/workflows/update-workflows.yaml @@ -36,7 +36,7 @@ jobs: if: ${{ needs.check_token.outputs.workflow == 'true' }} steps: - name: "Checkout Repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Update Workflows id: update diff --git a/code/episodes/docker-cli-toolkit.md b/code/episodes/docker-cli-toolkit.md new file mode 100644 index 0000000..110a05a --- /dev/null +++ b/code/episodes/docker-cli-toolkit.md @@ -0,0 +1,141 @@ + + + + +## Pulling and Listing Images +```bash +docker pull spuacv/spuc:latest +``` + +## The structure of a Docker command +Image: A diagram showing the syntactic structure of a Docker command + +## Listing Images +```bash +docker image ls +``` +## Inspecting +```bash +docker inspect spuacv/spuc:latest +``` +```bash +docker inspect spuacv/spuc:latest -f "Command: {{.Config.Cmd}}" +``` +```bash +docker inspect spuacv/spuc:latest -f "Entrypoint: {{.Config.Entrypoint}}" +``` +```bash +docker inspect spuacv/spuc:latest -f $'Command: {{.Config.Cmd}}\nEntrypoint: {{.Config.Entrypoint}}' +``` + +## Default Command +Image: A diagram representing the lifecycle of a container + +### Further examples of container lifecycle +Image: Further details and examples of the lifecycle of a container + + +## Running +```bash +docker run spuacv/spuc:latest +``` +```bash +docker run -d spuacv/spuc:latest +``` +## Listing Containers +```bash +docker ps +``` +```bash +docker ps -a +``` +## Logs +```bash +docker logs ecstatic_nightingale +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=moon\&brightness=100 +``` +## Exposing ports +```bash +docker run -d -p 8321:8321 spuacv/spuc:latest +``` +```bash +docker ps +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=moon\&brightness=100 +``` +```bash +docker logs unruffled_noyce +``` +## Setting the name of a container +```bash +docker run -d --name spuc_container -p 8321:8321 spuacv/spuc:latest +``` +```bash +docker stop unruffled_noyce +docker rm unruffled_noyce +``` + +### Killing containers +```bash +docker kill ecstatic_nightingale +``` + +```bash +docker run -d --name spuc_container -p 8321:8321 spuacv/spuc:latest +``` +```bash +docker logs -f spuc_container +``` + +### Logs + +## Executing commands in a running container +```bash +docker exec spuc_container cat config/print.config +``` +```bash +docker exec -it spuc_container bash +``` +```bash +apt update +apt install tree +tree +``` + +## Interactive sessions +```bash +docker run -it alpine:latest +``` + +## Reviving Containers +```bash +docker kill spuc_container +``` +```bash +docker start spuc_container +``` +```bash +docker stats +``` +## Cleaning up +```bash +docker kill spuc_container +``` +```bash +docker rm spuc_container +``` +```bash +docker image rm spuacv/spuc:latest +``` +```bash +docker system prune +``` +### Automatic cleanup +```bash +docker run -d --rm --name spuc_container -p 8321:8321 spuacv/spuc:latest +``` + + diff --git a/code/episodes/docker-compose-microservices.md b/code/episodes/docker-compose-microservices.md new file mode 100644 index 0000000..4416b5f --- /dev/null +++ b/code/episodes/docker-compose-microservices.md @@ -0,0 +1,70 @@ + + + + +## Microservices +## Microservices in Docker Compose +```yml +services: + proxy: + image: 'jc21/nginx-proxy-manager:latest' + ports: + - '80:80' + - '443:443' + depends_on: + - authelia + healthcheck: + test: ["CMD", "/bin/check-health"] + + whoami: + image: docker.io/traefik/whoami + + authelia: + image: authelia/authelia + depends_on: + lldap: + condition: service_healthy + volumes: + - ${PWD}/config/authelia/config:/config + environment: + AUTHELIA_DEFAULT_REDIRECTION_URL: https://whoami.${URL} + AUTHELIA_STORAGE_POSTGRES_HOST: authelia-postgres + AUTHELIA_AUTHENTICATION_BACKEND_LDAP_URL: ldap://apperture-ldap:3890 + + lldap: + image: nitnelave/lldap:stable + depends_on: + lldap-postgres: + condition: service_healthy + environment: + LLDAP_LDAP_BASE_DN: dc=example,dc=com + LLDAP_DATABASE_URL: postgres://user:pass@lldap-postgres/dbname + volumes: + - lldap-data:/data + + lldap-postgres: + image: postgres + volumes: + - lldap-postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U lldap"] + +volumes: + lldap-data: + lldap-postgres-data: +``` +Image: Apperture Services: Showing a user accessing WhoAmI via the web portal, which is protected by Authelia, which authenticates against an LDAP server, which pulls user data from a Postgres database. +## Combining Stacks +```yml +# SPUC docker-compose.yml + ++ networks: ++ apperture: ++ external: true ++ name: apperture_default +``` +Image: SPUC and Apperture Services: Showing a user accessing the SPUC interface via the web portal. +## Rapid extension +Image: SPUC and Apperture Services: Showing a user accessing the SPUC interface via the web portal, which is protected by Authelia, which authenticates against an LDAP server, which pulls user data from a Postgres database. The SPUC interface communicates with a Postgres database, a RabbitMQ message queue, a Telegraf sensor, and a MinIO object store. +## They Lived Happily Ever After +Image: Thank you for supporting the SPUA! diff --git a/code/episodes/docker-compose.md b/code/episodes/docker-compose.md new file mode 100644 index 0000000..b95723d --- /dev/null +++ b/code/episodes/docker-compose.md @@ -0,0 +1,460 @@ + + + + +## Running a container +```yml +services: +``` +```yml +services: + spuc: # The name of the service + image: spuacv/spuc:latest # The image to use +``` +```bash +docker compose up +``` +## Configuring the container +```bash +docker run -d --rm --name spuc_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -v stats.py:/spuc/plugins/stats.py -e EXPORT=true spuacv/spuc:latest --units iulu +``` +### Running in the background +```bash +$ docker compose up -d +``` +```bash +docker compose logs +``` +### Removing the container when it stops +```bash +docker compose down +``` +### Naming the container +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container # The name of the container +``` +```bash +docker compose up -d +``` + +#### Updating the compose file + +### Exporting a port +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=asteroid\&brightness=242 +``` +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container + ports: # Starts the list of ports to map + - 8321:8321 # Maps port 8321 on the host to port 8321 in the container +``` +```bash +docker compose up -d +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=asteroid\&brightness=242 +``` +### Bind mounts +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container + ports: + - 8321:8321 + volumes: # Starts the list of volumes/bind mounts + - ./print.config:/spuc/config/print.config # Bind mounts the print.config file +``` +```bash +docker compose up -d +``` +### Volumes +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container + ports: + - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output # Mounts the volume named spuc-volume +``` +```bash +docker compose up -d +``` +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container + ports: + - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output # Mounts the volume named spuc-volume + +volumes: # Starts section for declaring volumes + spuc-volume: # Declares a volume named spuc-volume +``` +```bash +docker compose up -d +``` +```bash +$ docker volume ls +$ docker compose down -v +$ docker volume ls +``` +### Setting an environment variable +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container + ports: + - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + environment: # Starts list of environment variables to set + - EXPORT=true # Sets the EXPORT environment variable to true + +volumes: + spuc-volume: +``` +```bash +docker compose up -d +docker compose logs +``` +### Overriding the default command +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container + ports: + - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + environment: + - EXPORT=true + command: ["--units", "iulu"] # Overrides the default command + +volumes: + spuc-volume: +``` +```bash +docker compose up -d +docker compose logs +``` +### Enabling the plugin +```yml +services: + spuc: + image: spuacv/spuc:latest + container_name: spuc_container + ports: + - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py # Mounts the stats.py plugin + environment: + - EXPORT=true + command: ["--units", "iulu"] + +volumes: + spuc-volume: +``` +```bash +docker compose up -d +``` +```bash +docker compose logs +``` +## Building containers in Docker Compose +```yml +services: + spuc: + # image: spuacv/spuc:latest + build: # Instead of using the 'image' key, we use the 'build' key + context: . # Sets the build context (the directory in which the Dockerfile is located) + dockerfile: Dockerfile # Sets the name of the Dockerfile + container_name: spuc_container + ports: + - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=true + command: ["--units", "iulu"] +``` +```bash +docker compose up --build -d +docker compose logs +``` + +#### Simpler Dockerfile +```Dockerfile +FROM spuacv/spuc:latest +RUN pip install pandas +``` + +## Connecting multiple services +### Adding SPUCSVi to our Docker Compose file +```yml +services: + spuc: + build: + context: . + dockerfile: Dockerfile + container_name: spuc_container + ports: + - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=true + command: ["--units", "iulu"] + + spucsvi: # Declare a new service named spucsvi + image: spuacv/spucsvi:latest # Specify the image to use + container_name: spucsvi_container # Name the container spucsvi + ports: # + - "8322:8322" # Map port 8322 on the host to port 8322 in the container + environment: # + - SPUC_URL=http://spuc:8321 # Specify the SPUC_URL environment variable + +volumes: + spuc-volume: +``` +```bash +docker compose up -d +docker compose logs +``` +### Networks +```yml +services: + spuc: + build: + context: . + dockerfile: Dockerfile + container_name: spuc_container + # ports: # We can remove these two lines + # - 8321:8321 + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=true + command: ["--units", "iulu"] + + spucsvi: + image: spuacv/spucsvi:latest + container_name: spucsvi + ports: + - "8322:8322" + environment: + - SPUC_URL=http://spuc:8321 + +volumes: + spuc-volume: +``` +```bash +docker compose up -d +``` + +#### Network names +```yml +services: + spuc: + build: + context: . + dockerfile: Dockerfile + container_name: spuc_container + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=true + command: ["--units", "iulu"] + networks: # Starts list of networks to connect this service to + - spuc_network # Connects to the spuc_network network + + spucsvi: + image: spuacv/spucsvi:latest + container_name: spucsvi + ports: + - "8322:8322" + environment: + - SPUC_URL=http://spuc:8321 + networks: # Starts list of networks to connect this service to + - spuc_network # Connects to the spuc_network network + +volumes: + spuc-volume: + +networks: # Starts section for declaring networks + spuc_network: # Declares a network for spuc + name: spuc_network # Specifies the name of the network +``` + +### Depends on +```yml +services: + spuc: + build: + context: . + dockerfile: Dockerfile + container_name: spuc_container + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=true + command: ["--units", "iulu"] + + spucsvi: + image: spuacv/spucsvi:latest + container_name: spucsvi + ports: + - "8322:8322" + environment: + - SPUC_URL=http://spuc:8321 + depends_on: # Starts section for declaring dependencies + - spuc # Declares that the spucsvi service depends on the spuc service + +volumes: + spuc-volume: +``` +```yml +services: + spuc: + build: + context: . + dockerfile: Dockerfile + container_name: spuc_container + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=true + command: ["--units", "iulu"] + healthcheck: # Starts section for declaring healthchecks + test: ["CMD", "curl", "--fail", "http://spuc:8321/export"] # Specifies the healthcheck command (ran from inside the container) + interval: 3s # Specifies the interval between healthchecks + timeout: 2s # Specifies the timeout for the healthcheck + retries: 5 # Specifies the number of retries before failing completely + + spucsvi: + image: spuacv/spucsvi:latest + container_name: spucsvi + ports: + - "8322:8322" + environment: + - SPUC_URL=http://spuc:8321 + depends_on: + spuc: # This changed from a list (- spuc) to a mapping (spuc:) + condition: service_healthy # Specifies further conditions for starting the service + +volumes: + spuc-volume: +``` + +#### Simulating a slow start +```yml +services: + spuc: + build: + context: . + dockerfile: Dockerfile + container_name: spuc_container + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=true + command: ["--units", "iulu"] + entrypoint: ["sh", "-c", "sleep 5 && python /spuc/spuc.py"] # Adds a sleep command to the entrypoint to simulate a slow start + healthcheck: + test: ["CMD", "curl", "--fail", "http://spuc:8321/export"] + interval: 3s + timeout: 2s + retries: 5 + + spucsvi: + image: spuacv/spucsvi:latest + container_name: spucsvi + ports: + - "8322:8322" + environment: + - SPUC_URL=http://spuc:8321 + depends_on: + spuc: + condition: service_healthy + +volumes: + spuc-volume: +``` +```bash +docker compose up -d +``` + + +#### Simulating an unhealthy service +```yml +services: + spuc: + build: + context: . + dockerfile: Dockerfile + container_name: spuc_container + volumes: + - ./print.config:/spuc/config/print.config + - spuc-volume:/spuc/output + - ./stats.py:/spuc/plugins/stats.py + environment: + - EXPORT=false # Sets the EXPORT environment variable to false + command: ["--units", "iulu"] + healthcheck: + test: ["CMD", "curl", "--fail", "http://spuc:8321/export"] + interval: 3s + timeout: 2s + retries: 5 + + spucsvi: + image: spuacv/spucsvi:latest + container_name: spucsvi + ports: + - "8322:8322" + environment: + - SPUC_URL=http://spuc:8321 + depends_on: + spuc: + condition: service_healthy + +volumes: + spuc-volume: +``` +```bash +docker compose up -d +``` + + + diff --git a/code/episodes/docker-desktop.md b/code/episodes/docker-desktop.md new file mode 100644 index 0000000..5d8f7da --- /dev/null +++ b/code/episodes/docker-desktop.md @@ -0,0 +1,213 @@ + + + + +## Getting images +Image: Docker Desktop being opened for the first time. + +### Step 1 +Image: Search and pull of SPUC container - Step 1 +### Step 2 +Image: Search and pull of SPUC container - Step 2 +### Step 3 +Image: Search and pull of SPUC container - Step 3 +### Step 4 +Image: Search and pull of SPUC container - Step 4 +### Step 5 +Image: Search and pull of SPUC container - Step 5 + +## Inspecting images +Image: Images list showing spuc, alpine and hello-world. + +### Step 1 +Image: Inspecting spuc image - Step 1 +### Step 2 +Image: Inspecting spuc image - Step 2 +### Step 3 +Image: Inspecting spuc image - Step 3 + +## Running containers +Image: Run button from Images tab. +Image: Run confirmation prompt. + +### Logs +Image: Logs tab in container from hello-world image. +### Inspect +Image: Inspect tab in container from hello-world image. +### Bind mounts +Image: Bind mounts tab in container from hello-world image. +### Exec +Image: Exec tab in container from hello-world image. +### Files +Image: Files tab in container from hello-world image. +### Stats +Image: Stats tab in container from hello-world image. + + +### Step 1 +Image: Clicking Start on the already run hello-world container - Step 1 +### Step 2 +Image: Clicking Start on the already run hello-world container - Step 2 + + +### Step 1 +Image: Running hello-world image for a second time - Step 1 +### Step 2 +Image: Running hello-world image for a second time - Step 2 +### Step 3 +Image: Running hello-world image for a second time - Step 3 + +Image: Containers list. +## Interacting with containers + +### Step 1 +Image: Optional settings for spuc - Step 1 +### Step 2 +Image: Optional settings for spuc - Step 2 +### Step 3 +Image: Optional settings for spuc - Step 3 + + +### Logs +Image: Logs tab in container from spuc image. +### Inspect +Image: Inspect tab in container from spuc image. +### Bind mounts +Image: Bind mounts tab in container from spuc image. +### Exec +Image: Exec tab in container from spuc image. +### Files +Image: Files tab in container from spuc image. +### Stats +Image: Stats tab in container from spuc image. + +Image: Containers list, spuc still running. + +### Spot a unicorn! +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=moon\&brightness=100 +``` +Image: Detecting a unicorn, spuc logs. + + + +### Step 1 +Image: Interacting with spuc terminal in the Exec tab - Step 1 +### Step 2 +Image: Interacting with spuc terminal in the Exec tab - Step 2 +### Step 3 +Image: Interacting with spuc terminal in the Exec tab - Step 3 +### Step 4 +Image: Interacting with spuc terminal in the Exec tab - Step 4 +### Step 5 +Image: Interacting with spuc terminal in the Exec tab - Step 5 +### Step 6 +Image: Interacting with spuc terminal in the Exec tab - Step 6 +### Step 7 +Image: Interacting with spuc terminal in the Exec tab - Step 7 + + +### Step 1 +Image: Stop the spuc container - Step 1 +### Step 2 +Image: Stop the spuc container - Step 2 +### Step 3 +Image: Stop the spuc container - Step 3 + + +### Step 1 +Image: Exec in fresh spuc container - Step 1 +### Step 2 +Image: Exec in fresh spuc container - Step 2 +### Step 3 +Image: Exec in fresh spuc container - Step 3 +### Step 4 +Image: Exec in fresh spuc container - Step 4 + +Image: Containers list after new run of spuc image. +## Reviving containers + +### Step 1 +Image: Reviving container spuc - Step 1 +### Step 2 +Image: Reviving container spuc - Step 2 +### Step 3 +Image: Reviving container spuc - Step 3 +### Step 4 +Image: Reviving container spuc - Step 4 + +## Naming containers + +### Step 1 +Image: Optional settings for spuc - Step 1 +### Step 2 +Image: Optional settings for spuc - Step 2 +### Step 3 +Image: Optional settings for spuc - Step 3 + + +### Step 1 +Image: Optional settings for spuc - Step 1 +### Step 2 +Image: Optional settings for spuc - Step 2 +### Step 3 +Image: Optional settings for spuc - Step 3 +### Step 4 +Image: Optional settings for spuc - Step 4 +### Step 5 +Image: Optional settings for spuc - Step 5 +### Step 6 +Image: Optional settings for spuc - Step 6 + +## Cleaning up + +### Step 1 +Image: Deleting container SPUC - Step 1 +### Step 2 +Image: Deleting container SPUC - Step 2 +### Step 3 +Image: Deleting container SPUC - Step 3 + + +### Step 1 +Image: Failing to delete image - Step 1 +### Step 2 +Image: Failing to delete image - Step 2 +### Step 3 +Image: Failing to delete image - Step 3 + + +### Step 1 +Image: Deleting containers - Step 1 +### Step 2 +Image: Deleting containers - Step 2 +### Step 3 +Image: Deleting containers - Step 3 +### Step 4 +Image: Deleting containers - Step 4 + + +### Step 1 +Image: Successfully deleting images - Step 1 +### Step 2 +Image: Successfully deleting images - Step 2 +### Step 3 +Image: Successfully deleting images - Step 3 + +## Limitations - Why not Docker Desktop? + +### Logs +Image: Logs tab in container from alpine image. +### Inspect +Image: Inspect tab in container from alpine image. +### Bind mounts +Image: Bind mounts tab in container from alpine image. +### Exec +Image: Exec tab in container from alpine image. +### Files +Image: Files tab in container from alpine image. +### Stats +Image: Stats tab in container from alpine image. + + + diff --git a/code/episodes/docker-hub.md b/code/episodes/docker-hub.md new file mode 100644 index 0000000..51e29b1 --- /dev/null +++ b/code/episodes/docker-hub.md @@ -0,0 +1,26 @@ + + + + +## Introducing the Docker Hub + +## Docker can be used without connecting to the Docker Hub + +## Exploring an example Docker Hub page +Image: Dockerhub\_landing +Image: Dockerhub\_search +Image: Dockerhub\_spuc +Image: Dockerhub\_spuc\_tags +Image: Dockerhub\_spuc_latest + +## Official Images +Image: Dockerhub\_python + + +## Choosing Container Images on Docker Hub + + +## Other sources of Container Images + + + diff --git a/code/episodes/docker-run-configuration.md b/code/episodes/docker-run-configuration.md new file mode 100644 index 0000000..7dca5f5 --- /dev/null +++ b/code/episodes/docker-run-configuration.md @@ -0,0 +1,48 @@ + + + + +## Setting the environment +```bash +docker stop spuc_container +docker run -d --rm --name spuc_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -e EXPORT=true spuacv/spuc:latest +docker logs spuc_container +``` +```bash +curl localhost:8321/export +``` +## Passing parameters (overriding the command) +```bash +docker inspect spuacv/spuc:latest -f "Entrypoint: {{.Config.Entrypoint}}\nCommand: {{.Config.Cmd}}" +``` +```bash +docker stop spuc_container +docker run -d --rm --name spuc_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -e EXPORT=true spuacv/spuc:latest --units iulu +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=pluto\&brightness=66 +curl localhost:8321/export +``` +```bash +docker stop spuc_container +docker volume rm spuc-volume +docker run -d --rm --name spuc_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -e EXPORT=true spuacv/spuc:latest --units iulu +curl -X PUT localhost:8321/unicorn_spotted?location=moon\&brightness=177 +curl -X PUT localhost:8321/unicorn_spotted?location=earth\&brightness=18 +curl -X PUT localhost:8321/unicorn_spotted?location=mars\&brightness=709 +curl -X PUT localhost:8321/unicorn_spotted?location=jupyter\&brightness=372 +curl -X PUT localhost:8321/unicorn_spotted?location=venus\&brightness=262 +curl -X PUT localhost:8321/unicorn_spotted?location=pluto\&brightness=66 +curl localhost:8321/export +``` +## Overriding the entrypoint +```bash +docker run -it --rm --entrypoint /bin/sh spuacv/spuc:latest +``` + +# ! Challenge: +# !! Solution: + + + + diff --git a/code/episodes/docker-volumes.md b/code/episodes/docker-volumes.md new file mode 100644 index 0000000..7734139 --- /dev/null +++ b/code/episodes/docker-volumes.md @@ -0,0 +1,82 @@ + + + + +## Making our data persist +## Volumes +```bash +docker kill spuc_container +docker run -d --rm --name spuc_container -p 8321:8321 -v spuc-volume:/spuc/output spuacv/spuc:latest +``` +```bash +docker volume ls +``` + +### Inspecting the volume +```bash +docker volume inspect spuc-volume +``` + +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=moon\&brightness=100 +curl -X PUT localhost:8321/unicorn_spotted?location=earth\&brightness=10 +curl -X PUT localhost:8321/unicorn_spotted?location=mars\&brightness=400 +``` +```bash +docker exec spuc_container cat /spuc/output/unicorn_sightings.txt +``` +```bash +docker kill spuc_container +docker ps -a +``` +```bash +docker run -d --rm --name spuc_container -p 8321:8321 -v spuc-volume:/spuc/output spuacv/spuc:latest +docker exec spuc_container cat /spuc/output/unicorn_sightings.txt +``` +## Bind mounts +```bash +docker kill spuc_container +docker run -d --rm --name spuc_container -p 8321:8321 -v ./spuc/output:/spuc/output spuacv/spuc:latest +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=mercury\&brightness=400 +cat spuc/output/unicorn_sightings.txt +``` +```bash +docker kill spuc_container +ls spuc/output +``` +```bash +docker run -d --rm --name spuc_container -p 8321:8321 -v ./spuc/output:/spuc/output spuacv/spuc:latest +cat spuc/output/unicorn_sightings.txt +``` +```bash +ls -l spuc/unicorn_sightings.txt +``` +### Bind mount files +```bash +echo "::::: {time} Unicorn number {count} spotted at {location}! Brightness: {brightness} {units}" > print.config +``` +```bash +docker kill spuc_container +docker run -d --rm --name spuc_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output spuacv/spuc:latest +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=jupyter\&brightness=210 +docker logs spuc_container +``` +```bash +echo "::::: Unicorn number {count} spotted at {location}! Brightness: {brightness} {units}" > print.config +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=venus\&brightness=148 +docker logs spuc_container +``` + +# ! Challenge: +## Common mistakes with volumes +# !! Solution: + + + + diff --git a/code/episodes/dockerfiles.md b/code/episodes/dockerfiles.md new file mode 100644 index 0000000..c368554 --- /dev/null +++ b/code/episodes/dockerfiles.md @@ -0,0 +1,149 @@ + + + + +```python +from __main__ import app +from __main__ import file_path + +import pandas as pd +import os + +@app.route("/stats", methods=["GET"]) +def stats(): + if not os.path.exists(file_path): + return {"message": "No unicorn sightings yet!"} + + with open(file_path) as f: + df = pd.read_csv(f) + df = df.iloc[:, 1:] + stats = df.describe() + return stats.to_json() +``` +```bash +docker kill spuc_container +docker run --rm --name spuc_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -v ./stats.py:/spuc/plugins/stats.py -e EXPORT=true spuacv/spuc:latest --units iulu +``` +## Creating Docker Images +```Dockerfile +FROM spuacv/spuc:latest +``` +```bash +docker build -t spuc-stats ./ +``` + +## Context + + +### The Dockerfile name +```bash +docker build -t spuc-stats -f my_recipe ./ +``` + +```bash +docker image ls +``` +```bash +docker run --rm spuc-stats +``` +```bash +docker run --rm --name spuc-stats_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -v ./stats.py:/spuc/plugins/stats.py -e EXPORT=true spuc-stats --units iulu +``` +```Dockerfile +RUN pip install pandas +``` +```bash +$ docker build -t spuc-stats ./ +``` +```bash +docker run --rm --name spuc-stats_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -v ./stats.py:/spuc/plugins/stats.py -e EXPORT=true spuc-stats --units iulu +``` +```bash +curl localhost:8321/stats +``` +## COPY +```Dockerfile +COPY stats.py /spuc/plugins/stats.py +``` +```bash +docker build -t spuc-stats ./ +``` + +## Layers + +```bash +docker run --rm --name spuc-stats_container -p 8321:8321 -v ./print.config:/spuc/config/print.config -v spuc-volume:/spuc/output -e EXPORT=true spuc-stats --units iulu +``` +```Dockerfile +COPY print.config /spuc/config/print.config +``` +```bash +docker build -t spuc-stats ./ +docker run --rm --name spuc_container -p 8321:8321 -v spuc-volume:/spuc/output -e EXPORT=True spuc-stats --units iulu +``` +```bash +curl -X PUT localhost:8321/unicorn_spotted?location=saturn\&brightness=87 +``` +```bash +docker logs spuc_container +``` +## ENV +```Dockerfile +ENV EXPORT=True +``` +```bash +docker build -t spuc-stats ./ +docker run --rm --name spuc-stats_container -p 8321:8321 -v spuc-volume:/spuc/output spuc-stats --units iulu +``` + +## ARG + + +## Layers - continued +```Dockerfile +FROM spuacv/spuc:latest + +ENV EXPORT=True + +RUN pip install pandas + +COPY stats.py /spuc/plugins/stats.py +COPY print.config /spuc/config/print.config +``` +```bash +docker build -t spuc-stats ./ +``` + +## ENTRYPOINT and CMD +```Dockerfile +ENTRYPOINT ["python", "/spuc/spuc.py"] +CMD ["--units", "iulu"] +``` +```bash +docker build -t spuc-stats ./ +docker run --rm --name spuc-stats_container -p 8321:8321 -v spuc-volume:/spuc/output spuc-stats +``` +## Building containers from the ground up +```Dockerfile +FROM python:3.12-slim + +RUN apt update +RUN apt install -y curl + +WORKDIR /spuc + +COPY ./requirements.txt /spuc/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /spuc/requirements.txt + +COPY ./*.py /spuc/ +COPY ./config/*.config /spuc/config/ +RUN mkdir /spuc/output + +EXPOSE 8321 + +ENTRYPOINT ["python", "/spuc/spuc.py"] +CMD ["--units", "iuhc"] +``` + + diff --git a/code/episodes/introduction.md b/code/episodes/introduction.md new file mode 100644 index 0000000..a7592a5 --- /dev/null +++ b/code/episodes/introduction.md @@ -0,0 +1,9 @@ + + + + +## Docker +### Why Containers? +### Why Docker? +## The Space Purple Unicorn Association +Image: SPUA logo diff --git a/code/extract_episodes_code.py b/code/extract_episodes_code.py new file mode 100644 index 0000000..954c632 --- /dev/null +++ b/code/extract_episodes_code.py @@ -0,0 +1,82 @@ +# Script to extract code blocks to be used for teaching notes +# Usage: +# From the repository's base directory, call as: +# python3 code/extract_episodes_code.py + +import os + + +def extract_episodes_code( + episode_names=None, output_dir="code/episodes", with_blocks=True, quiet=False +): + # Create the list of episode files if not provided + if episode_names is None: + episode_files = [f for f in os.listdir("episodes") if f.endswith(".Rmd")] + episode_names = [os.path.join("episodes", f) for f in episode_files] + + # Create the "code" folder if it does not exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Extract the code blocks, headers and image alt text from each episode + for episode_name in episode_names: + if not quiet: + print(f"Processing episode {episode_name}") + + # Read the file content + with open(episode_name, "r") as episode_file: + lines = episode_file.readlines() + + in_code_block = False + code_blocks = [] + code_block = [] + + for line in lines: + # Check for the start of a code block + if not in_code_block: + # Adds headers + if line.startswith("#"): + code_blocks.append(line.rstrip()) + # Adds empty lines with ::: delimiters + if line.startswith(":::"): + if "challenge" in line: + code_blocks.append("\n# ! Challenge:") + elif "solution" in line: + code_blocks.append("# !! Solution:") + else: + code_blocks.append("") + # Adds image alt text + if line.startswith("!"): + alt_text = line.rstrip().split("alt")[1][2:-2] + code_blocks.append(f"Image: {alt_text}") + # Starts code_block + if line.startswith("```") and len(line.strip()) > 3: + in_code_block = True + if in_code_block: + # Accumulate lines within the code block + code_block.append(line.rstrip()) + if line.strip() == "```": + in_code_block = False + # Skip outputs + if not code_block[0].strip() == ("```output"): + # Append full code_block to code_blocks array + if with_blocks: + code_blocks.append("\n".join(code_block)) + else: + code_blocks.append("\n".join(code_block[1:-1])) + code_block = [] + + # Write code blocks to markdown file + output_file_name = os.path.join( + output_dir, os.path.basename(episode_name).replace(".Rmd", ".md") + ) + with open(output_file_name, "w") as out_file: + for block in code_blocks: + out_file.write(block + "\n") + + if not quiet: + print(f"Saved code blocks to {output_file_name}") + + +if __name__ == "__main__": + extract_episodes_code() diff --git a/episodes/docker-compose.Rmd b/episodes/docker-compose.Rmd index 52da29b..67e41f7 100644 --- a/episodes/docker-compose.Rmd +++ b/episodes/docker-compose.Rmd @@ -341,7 +341,7 @@ services: ```bash docker compose up -d ``` -``` output +```output [+] Running 2/2 ✔ Network docker_intro_default Created 0.1s ✔ Container spuc_container Started 0.2s