{"id":15687341,"url":"https://github.com/lirantal/docker-images-security-workshop","last_synced_at":"2025-08-10T15:21:54.569Z","repository":{"id":66230643,"uuid":"235219891","full_name":"lirantal/docker-images-security-workshop","owner":"lirantal","description":"Docker Image Security Workshop for Best Practices","archived":false,"fork":false,"pushed_at":"2020-02-20T22:17:25.000Z","size":62,"stargazers_count":9,"open_issues_count":0,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-28T16:58:16.496Z","etag":null,"topics":["best-practices","docker-images","security-vulnerabilities","snyk"],"latest_commit_sha":null,"homepage":null,"language":"Dockerfile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc-by-sa-4.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lirantal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-20T23:43:02.000Z","updated_at":"2022-07-02T13:30:11.000Z","dependencies_parsed_at":"2023-03-30T04:34:10.734Z","dependency_job_id":null,"html_url":"https://github.com/lirantal/docker-images-security-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lirantal/docker-images-security-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lirantal%2Fdocker-images-security-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lirantal%2Fdocker-images-security-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lirantal%2Fdocker-images-security-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lirantal%2Fdocker-images-security-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lirantal","download_url":"https://codeload.github.com/lirantal/docker-images-security-workshop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lirantal%2Fdocker-images-security-workshop/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269740830,"owners_count":24467855,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["best-practices","docker-images","security-vulnerabilities","snyk"],"created_at":"2024-10-03T17:47:38.343Z","updated_at":"2025-08-10T15:21:54.543Z","avatar_url":"https://github.com/lirantal.png","language":"Dockerfile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Docker Image Security workshop\n\n## 1. Challenge: Find the most vulnerability-free image\n\nDemo OS-level vulnerabilities made possible by a vulnerability in a library installed on the operating system, and used by an application: [https://github.com/lirantal/goof-container-breaking-in](https://github.com/lirantal/goof-container-breaking-in)\n\nIn this challenge you should find and fix vulnerabilities in docker images that you test and build in a local environment, or from your CI.\n\nStarting point is this goofy project called `docker-goof` which uses an old version of Node.js runtime as a docker image tag.\n\n1. Clone the project: [https://github.com/snyk/docker-goof](https://github.com/snyk/docker-goof)\n2. Inside it you will find a `Dockerfile` with the image to use\n3. Find how many vulnerabilities (low, medium, and high) the image has. Are there any interesting vulnerabilities?\n\n\u003cdetails\u003e\u003csummary\u003eHint\u003c/summary\u003e\n\u003cbr/\u003e\n\nWe'll get started with scanning a docker image using the Snyk CLI which is free to use and scan.\n\n### Install the Snyk CLI\n\nYou can use the open source Snyk CLI to scan the image.\n\n* [Install Snyk on Windows](https://support.snyk.io/hc/en-us/articles/360003812538#UUID-ac39f35d-8608-e949-613d-24333ced4d42)\n* [Install Snyk on macOS](https://support.snyk.io/hc/en-us/articles/360003812538#UUID-876089c6-d195-a81e-4c7a-21354f788306)\n* [Install Snyk on Linux](https://github.com/snyk/snyk/releases) and other self-contained executables for Windows and macOS.\n\nSee [Snyk CLI install instructions](https://support.snyk.io/hc/en-us/articles/360003812458-Getting-started-with-the-CLI) to get started for the CLI\n\n### Scan a docker image for security vulnerabilities\n\nTo scan an image you'll need to pull the docker image to your host locally and then scan it:\n\n```\n$ docker pull \u003cimage\u003e\n$ snyk test --docker \u003cimage\u003e\n```\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n4. Did you find vulnerabilities in the Node.js runtime as well? give an example of one of them and which version of Node.js will fix it.\n5. How would you fix these vulnerabilities?\n6. Find which official Node.js image you could switch to in order to lower your vulnerabilities footprint without disrupting the product and development teams too much.\n\n\u003cdetails\u003e\u003csummary\u003eHint\u003c/summary\u003e\n\u003cbr/\u003e\n\nIf the Snyk CLI is provided with a Dockerfile it will give you a remediation advice so you can make a conscious decision of which image you could move to in order to lower the security vulnerabilities foot-print.\n\nWhat happens if you provide the Snyk CLI with the Dockerfile as well?\n\n```\nsnyk test --docker \u003cimage\u003e --file=Dockerfile\n```\n\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eSolution\u003c/summary\u003e\n\u003cbr/\u003e\n\n```\nsnyk test --docker node:10.4.0 --file=Dockerfile\n```\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\nDon't forget -\nLowest vulnerabilities \u0026 ease of upgrade wins the challenge!\n\n### Registry workflow\n\nFind and fix vulnerabilities in docker images in a Docker Hub registry or others.\n\nBonus challenge - how do you monitor your docker images on a container registry and do you have an actionable advice as to which image and tag you should change to in order to lower the footprint of your image's security vulnerabilities?\n\n## 2. Challenge: Don't steal my immutability!\n\nYour team needs to update to the latest version of Node.js 10 LTS to get fixes and keep up to date with security vulnerabilities.\n\nYou do the obvious:\n\n```\ndocker pull node:10\n```\n\n1. Can you think of some downsides to pulling the image this way?\n\n### Use fixed tags for immutability\n\nWhen you pulled the image you specified the image name and a tag. But what's the implications of using a tag like that?\n\n\u003e Each Docker image can have multiple tags, which are variants of the same images. The most common tag is _latest_, which represents the latest version of the image. Image tags are not immutable, and the author of the images can publish the same tag multiple times.\n\u003e This means that the base image for your Docker file might change between builds. This could result in inconsistent behavior because of changes made to the base image.\n\n_source: [10 Docker Image Security Best Practices](https://snyk.io/blog/10-docker-image-security-best-practices/)_\n\nThe above best practices document stresses that we should be as specific as possible in our tags, and ideally we'd pull in the image by its SHA256 reference.\n\n2. Pull in the image based on its SHA256\n\n\u003cdetails\u003e\u003csummary\u003eHint 1\u003c/summary\u003e\n\u003cbr/\u003e\n\nIf you pulled the `node:10` image, take a look at the output\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eHint2\u003c/summary\u003e\n\u003cbr/\u003e\n\nYou're looking for the `Digest` key in the output of `docker pull`\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eSolution: how to pull the image by its SHA256\u003c/summary\u003e\n\u003cbr/\u003e\n   \n```\ndocker pull node@sha256:bdc6d102e926b70690ce0cc0b077d450b1b231524a69b874912a9b337c719e6e\n```\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n## 3. Challenge: Really bad practices by default\n\nReminder: if you're coming here from previous challenges, did you remember to turn Docker's Content Trust policy off? Right, you need to do that:\n\n```\nexport DOCKER_CONTENT_TRUST=0\n```\n\nHow do you help your devops and developer engineers to validate the proper configuration of a `Dockerfile` when they are building them? Aaaaaaaaa-utomation!\n\nWe'll visit a couple of static code analysis tools to help us find out issues in a `Dockerfile`, or what us in the JavaScript land like to call - linters. There are a couple I recommend, and you're welcome to try both of them:\n\n1. [Dockle](https://github.com/goodwithtech/dockle) - Puts a focus on scanning the image through its layers.\n2. [Hadolint](https://github.com/hadolint/hadolint) - Statically analyze the Dockerfile\n\nFor this challenge we'll work with the project in the `bad-defaults/` directory.\nUse any of the linters above and test the Docker file/docker image.\n\nOnly after you found issues with either or both of the above linters should you continue to the issues below in order to fix them.\n\n### Container is running as root\n\n1. Build the bad defaults image\n\n\u003cdetails\u003e\u003csummary\u003eHint\u003c/summary\u003e\n\u003cbr/\u003e\n   \n```\ndocker build -t best-practices .\n```\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n2. Check if the container is running with the root user. If so, that's not good.\n\n\u003cdetails\u003e\u003csummary\u003eSolution\u003c/summary\u003e\n\u003cbr/\u003e\n   \n```\ndocker run -it --rm best-practices:latest sh\n```\n\nInside the container run:\n\n```\nwhoami\n```\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n3. Fix the issue so that the container uses a non-privileged user\n\n\u003cdetails\u003e\u003csummary\u003eSolution\u003c/summary\u003e\n\u003cbr/\u003e\nUpdate the Dockerfile to make use of the built-in `node` user:\n   \n```\n...\nUSER node\nCMD node index.js\n```\n\nNow build the container and run again to check which is user is being used.\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n## 4. Challenge: All your secrets belong to me!\n\nDid the `Dockerfile` smell somewhat fishy to you in the previous challenge?\nIt was smelly of secrets and tokens!\n\nI am specifically referring to this entry in the `Dockerfile`:\n\n```\nRUN echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" \u003e .npmrc\n```\n\n1. First assignment, can you think about why this is bad?\n   After all, we're building the image in an internal environment and only we pass that secret token at build time. No one else sees it.\n\n\u003cdetails\u003e\u003csummary\u003eHint 1\u003c/summary\u003e\n\u003cbr/\u003e\n\nThe `.npmrc` file contains sensitive information, such as a token which is used for read/write for private packages on a registry. If the container is compromised, users will be able to access it.\n\nCan you think of a simple vulnerability in an application that will allow a malicious attacker to easily get to the `.npmrc` file?\n\nYou can build it yourself and try:\n\n```\nexport NPM_TOKEN=\u003cnpm token\u003e\ndocker build -t best-practices --build-arg NPM_TOKEN=$NPM_TOKEN .\n```\n\nNow login to the container and validate the value of `.npmrc`:\n\n```\ndocker run -it --rm best-practices sh\n```\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n2. Can you think of some other ways to fix this issue of secrets leaking? Below are hints for 2 \"solutions\". They might prove like a good idea for you but we explain why they shouldn't be followed.\n\n\u003cdetails\u003e\u003csummary\u003eBad practice 1\u003c/summary\u003e\n\u003cbr/\u003e\n\nYou remember to remove the token, such as:\n\n```\nRUN echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" \u003e .npmrc\nRUN npm install\nRUN rm .npmrc\n```\n\nNice, but not really good.\nEvery `RUN` creates another layer, all of which are later inspect-able and leave a trace. This means that if the image itself is ever leaked or made public then sensitive data exists inside it in the form of the `.npmrc` file.\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eBad practice 2\u003c/summary\u003e\n\u003cbr/\u003e\n\nYou understand the concept of Docker layers so you put all of this into one command to make sure there's no trace, something like this:\n\n```\nRUN echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" \u003e .npmrc \u0026\u0026 \\\n    npm install \u0026\u0026 \\\n    rm .npmrc\n```\n\nHowever, Docker has this thing called commits history which it uses to save metadata about the way the image was built and this is why you should never really use environment variables such as build arguments for sensitive storage such as passwords, API keys, tokens.\n\nRead more about [Docker history here](https://docs.docker.com/engine/reference/commandline/history/).\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eHint: for proper solution\u003c/summary\u003e\n\u003cbr/\u003e\n\nWhat if you could create a Docker image without the `.npmrc` file in it?\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eSolution\u003c/summary\u003e\n\u003cbr/\u003e\n\nLet's use multi-stage builds to fix it!\n\nUpdate the `Dockerfile` so that the first image is used as a base to install all of our npm dependencies and build what is required. To do that, update the FROM instruction as follows:\n\n```\nFROM bitnami/node:latest AS build\n```\n\nAs well as remove the `CMD` instruction which isn't needed.\nThen have another section in the Dockerfile for the \"production\" image, which should use the app directory which is now ready for use from the previous build image.\n\nFollowing is an example:\n\n```\nFROM bitnami/node:latest\nRUN mkdir ~/project\nCOPY --from=build /app/~/project ~/project\nWORKDIR ~/project\nCMD node index.js\n```\n\nAn example of a full multi-stage Node.js docker image build Dockerfile:\n\n```\nFROM node:12 AS build\nRUN mkdir ~/project\nCOPY app/. ~/project\nWORKDIR ~/project\nRUN echo \"//registry.npmjs.org/:_authToken=$NPM_TOKEN\" \u003e .npmrc\nRUN npm install\n\nFROM node:12-slim\nRUN mkdir ~/project\nCOPY app/. ~/project\nCOPY --from=build /~/project/node_modules ~/project/node_modules\nWORKDIR ~/project\nCMD node index.js\n```\n\n\u003cbr/\u003e\n\u003c/details\u003e\n\n# Author\n\n**Docker image security best practices workshop** © [Liran Tal](https://github.com/lirantal), Released under [CC BY-SA 4.0](./LICENSE) License.\n\n# License\n\n\u003ca rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"\u003e\u003cimg alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-sa/4.0/88x31.png\" /\u003e\u003c/a\u003e\u003cbr /\u003eThis work is licensed under a \u003ca rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"\u003eCreative Commons Attribution-ShareAlike 4.0 International License\u003c/a\u003e.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flirantal%2Fdocker-images-security-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flirantal%2Fdocker-images-security-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flirantal%2Fdocker-images-security-workshop/lists"}