From b21179ed3c6a6a31ad7b5b30f0eb93f352cfabea Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 23 May 2024 10:39:34 -0400 Subject: [PATCH 1/8] chore: gitignore; fix author string --- .gitignore | 2 ++ pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a0fabef..a138999 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .bash_history .python_history .cache/ +.pytest_cache +__pycache__ diff --git a/pyproject.toml b/pyproject.toml index 905559b..e72ddb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "python-starter" version = "0.1.0" description = "Boilerplate code for developing a python app in docker" -authors = ["Monique Rio , Samuel Sciolla , Lianet Sepulveda Torres , "] +authors = [ "Monique Rio , Samuel Sciolla , Lianet Sepulveda Torres "] readme = "README.md" packages = [{include = "python_starter"}] From 3a84defd5330118a0a7fc322c7c75ea5a44da644 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 23 May 2024 10:44:08 -0400 Subject: [PATCH 2/8] Add file structure --- python_starter/__init__.py | 0 python_starter/sample.py | 6 ++++++ tests/__init__.py | 0 tests/test_sample.py | 4 ++++ 4 files changed, 10 insertions(+) create mode 100644 python_starter/__init__.py create mode 100644 python_starter/sample.py create mode 100644 tests/__init__.py create mode 100644 tests/test_sample.py diff --git a/python_starter/__init__.py b/python_starter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_starter/sample.py b/python_starter/sample.py new file mode 100644 index 0000000..af5e1d3 --- /dev/null +++ b/python_starter/sample.py @@ -0,0 +1,6 @@ +class Sample: + def __init__(self): + pass + + def add_one(self, a): + return(a + 1) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_sample.py b/tests/test_sample.py new file mode 100644 index 0000000..d268941 --- /dev/null +++ b/tests/test_sample.py @@ -0,0 +1,4 @@ +from python_starter.sample import Sample + +def test_add_one(): + assert Sample().add_one(21) == 22 \ No newline at end of file From 832142f963c99c3ac5e1b043bc8356c81d9d59f6 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 23 May 2024 14:24:36 -0400 Subject: [PATCH 3/8] add vscode stuff to .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index a138999..23e9435 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ .cache/ .pytest_cache __pycache__ +.dotnet/ +.vscode-server +.local +.gitconfig \ No newline at end of file From 0ad038f0509382b8229d85ce42b1e7a7c3c7924e Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Fri, 24 May 2024 09:24:16 -0400 Subject: [PATCH 4/8] adds dev-container setup --- .devcontainer/devcontainer.json | 45 ++++++++++++++++++++++++ .devcontainer/docker-compose.yml | 26 ++++++++++++++ .dockerignore | 1 + .github/dependabot.yml | 12 +++++++ .gitignore | 3 +- Dockerfile | 59 ++++++++++++++++++-------------- compose.yml | 2 +- 7 files changed, 120 insertions(+), 28 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml create mode 100644 .dockerignore create mode 100644 .github/dependabot.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..aed8244 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,45 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose +{ + "name": "Existing Docker Compose (Extend)", + + // Update the 'dockerComposeFile' list if you have more compose files or use different names. + // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. + "dockerComposeFile": [ + "../compose.yml", + "docker-compose.yml" + ], + + // The 'service' property is the name of the service for the container that VS Code should + // use. Update this value and .devcontainer/docker-compose.yml to the real service name. + "service": "app", + + // The optional 'workspaceFolder' property is the path VS Code should open by default when + // connected. This is typically a file mount in .devcontainer/docker-compose.yml + "workspaceFolder": "/app", + "features": { + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers-contrib/features/ruff:1": {} + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line if you want start specific services in your Docker Compose config. + // "runServices": [], + + // Uncomment the next line if you want to keep your containers running after VS Code shuts down. + // "shutdownAction": "none", + + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "cat /etc/os-release", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..6082339 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.8' +services: + # Update this to the name of the service you want to work with in your docker-compose.yml file + app: + # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer + # folder. Note that the path of the Dockerfile and context is relative to the *primary* + # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" + # array). The sample below assumes your primary file is in the root of your project. + # + # build: + # context: . + # dockerfile: .devcontainer/Dockerfile + + # volumes: + # # Update this to wherever you want VS Code to mount the folder of your project + # # - ..:/workspaces:cached + + # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. + # cap_add: + # - SYS_PTRACE + # security_opt: + # - seccomp:unconfined + + # Overrides default command so things don't shut down after the process ends. + command: /bin/sh -c "while sleep 1000; do :; done" + diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1d17dae --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.venv diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.gitignore b/.gitignore index 23e9435..a4d78ae 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ __pycache__ .dotnet/ .vscode-server .local -.gitconfig \ No newline at end of file +.gitconfig +.venv \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b5b783b..828db1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,9 +16,25 @@ ARG POETRY_VERSION # true = development / false = production ARG DEV +ARG UID=1000 +ARG GID=1000 + + +# Create our users here in the last layer or else it will be lost in the previous discarded layers +# Create a system group named "app_user" with the -r flag +RUN groupadd -g ${GID} -o app +RUN useradd -m -d /app -u ${UID} -g ${GID} -o -s /bin/bash app + + # Set the working directory to /app WORKDIR /app +CMD ["tail", "-f", "/dev/null"] + +FROM base as poetry + +RUN pip install poetry==${POETRY_VERSION} + # Use this page as a reference for python and poetry environment variables: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONUNBUFFERED # Ensure the stdout and stderr streams are sent straight to terminal, then you can see the output of your application ENV PYTHONUNBUFFERED=1\ @@ -34,49 +50,40 @@ ENV PYTHONUNBUFFERED=1\ POETRY_VIRTUALENVS_IN_PROJECT=1 \ POETRY_CACHE_DIR=/tmp/poetry_cache -RUN pip install poetry==${POETRY_VERSION} +FROM poetry as build + -# Install the app. Just copy the files needed to install the dependencies +# Just copy the files needed to install the dependencies COPY pyproject.toml poetry.lock README.md ./ + # Poetry cache is used to avoid installing the dependencies every time the code changes, we will keep this folder in development environment and remove it in production # --no-root, poetry will install only the dependencies avoiding to install the project itself, we will install the project in the final layer # --without dev to avoid installing dev dependencies, we do not need test and linters in production environment # --with dev to install dev dependencies, we need test and linters in development environment # --mount, mount a folder for plugins with poetry cache, this will speed up the process of building the image -RUN if [ {${DEV}} ]; then \ - echo "Installing dev dependencies"; \ - poetry install --no-root --with dev \ - else \ - echo "Skipping dev dependencies"; \ - poetry install --no-root --without dev && rm -rf ${POETRY_CACHE_DIR}; \ - fi +RUN poetry install --no-root --without dev && rm -rf ${POETRY_CACHE_DIR}; -# Set up our final runtime layer -FROM python:3.11-slim-bookworm as runtime -ARG UID=1000 -ARG GID=1000 +FROM poetry as development +RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \ + git -# Create our users here in the last layer or else it will be lost in the previous discarded layers -# Create a system group named "app_user" with the -r flag -RUN groupadd -g ${GID} -o app -RUN useradd -m -d /app -u ${UID} -g ${GID} -o -s /bin/bash app +# Switch to the non-root user "user" +USER app + +FROM base as production +# Switch to the non-root user "user" RUN mkdir -p /venv && chown ${UID}:${GID} /venv -RUN which pip && sleep 10 + +USER app # By adding /venv/bin to the PATH the dependencies in the virtual environment # are used ENV VIRTUAL_ENV=/venv \ PATH="/venv/bin:$PATH" -COPY --chown=${UID}:${GID} --from=base "/app/.venv" ${VIRTUAL_ENV} - -# Switch to the non-root user "user" -USER app - -WORKDIR /app - COPY --chown=${UID}:${GID} . /app -CMD ["tail", "-f", "/dev/null"] +COPY --chown=${UID}:${GID} --from=build "/app/.venv" ${VIRTUAL_ENV} + diff --git a/compose.yml b/compose.yml index 233dd81..0fa025a 100644 --- a/compose.yml +++ b/compose.yml @@ -2,7 +2,7 @@ services: app: build: context: . - target: runtime + target: development dockerfile: Dockerfile args: UID: ${UID:-1000} From 767ddeb29c742004176f8ac2ae757da83c30dd02 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Fri, 24 May 2024 13:31:05 +0000 Subject: [PATCH 5/8] tidy Dockerfile. Add comments and spacing --- Dockerfile | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 828db1e..0554bf1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,6 @@ FROM python:3.11-slim-bookworm as base # Allowing the argumenets to be read into the dockerfile. Ex: .env > compose.yml > Dockerfile ARG POETRY_VERSION -# true = development / false = production -ARG DEV - ARG UID=1000 ARG GID=1000 @@ -31,6 +28,7 @@ WORKDIR /app CMD ["tail", "-f", "/dev/null"] +# Both build and development need poetry, so it is its own step. FROM base as poetry RUN pip install poetry==${POETRY_VERSION} @@ -51,12 +49,9 @@ ENV PYTHONUNBUFFERED=1\ POETRY_CACHE_DIR=/tmp/poetry_cache FROM poetry as build - - # Just copy the files needed to install the dependencies COPY pyproject.toml poetry.lock README.md ./ - # Poetry cache is used to avoid installing the dependencies every time the code changes, we will keep this folder in development environment and remove it in production # --no-root, poetry will install only the dependencies avoiding to install the project itself, we will install the project in the final layer # --without dev to avoid installing dev dependencies, we do not need test and linters in production environment @@ -64,7 +59,7 @@ COPY pyproject.toml poetry.lock README.md ./ # --mount, mount a folder for plugins with poetry cache, this will speed up the process of building the image RUN poetry install --no-root --without dev && rm -rf ${POETRY_CACHE_DIR}; - +# We want poetry on in development FROM poetry as development RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \ git @@ -72,18 +67,17 @@ RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \ # Switch to the non-root user "user" USER app +# We don't want poetry on in production, so we copy the needed files form the build stage FROM base as production # Switch to the non-root user "user" RUN mkdir -p /venv && chown ${UID}:${GID} /venv -USER app - # By adding /venv/bin to the PATH the dependencies in the virtual environment # are used ENV VIRTUAL_ENV=/venv \ PATH="/venv/bin:$PATH" COPY --chown=${UID}:${GID} . /app - COPY --chown=${UID}:${GID} --from=build "/app/.venv" ${VIRTUAL_ENV} +USER app \ No newline at end of file From 0c0375694bf8dc9d84c579c97f7aea5503adb2a1 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Fri, 24 May 2024 13:37:35 +0000 Subject: [PATCH 6/8] add ssh to devcontainers so that we can send to github --- .devcontainer/devcontainer.json | 3 ++- .gitignore | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index aed8244..3c0b18e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,7 +19,8 @@ "workspaceFolder": "/app", "features": { "ghcr.io/devcontainers/features/git:1": {}, - "ghcr.io/devcontainers-contrib/features/ruff:1": {} + "ghcr.io/devcontainers-contrib/features/ruff:1": {}, + "ghcr.io/devcontainers/features/sshd:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/.gitignore b/.gitignore index a4d78ae..4c4b010 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ __pycache__ .vscode-server .local .gitconfig -.venv \ No newline at end of file +.venv +.ssh \ No newline at end of file From 96761bfec10b546d0e94af207bf9cf278f94a75c Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 30 May 2024 14:35:32 +0000 Subject: [PATCH 7/8] Took out unnecessary things --- .devcontainer/devcontainer.json | 4 ++-- Dockerfile | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3c0b18e..360e6ae 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,8 +6,8 @@ // Update the 'dockerComposeFile' list if you have more compose files or use different names. // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. "dockerComposeFile": [ - "../compose.yml", - "docker-compose.yml" + "../compose.yml" + // "docker-compose.yml" ], // The 'service' property is the name of the service for the container that VS Code should diff --git a/Dockerfile b/Dockerfile index 0554bf1..76d680b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,8 +61,6 @@ RUN poetry install --no-root --without dev && rm -rf ${POETRY_CACHE_DIR}; # We want poetry on in development FROM poetry as development -RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \ - git # Switch to the non-root user "user" USER app @@ -80,4 +78,4 @@ ENV VIRTUAL_ENV=/venv \ COPY --chown=${UID}:${GID} . /app COPY --chown=${UID}:${GID} --from=build "/app/.venv" ${VIRTUAL_ENV} -USER app \ No newline at end of file +USER app From 0dfb0d918c411b22abd83f38940137c08d02ce88 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 30 May 2024 17:30:45 +0000 Subject: [PATCH 8/8] Add comments to better describe the changes. --- .devcontainer/devcontainer.json | 5 +++- .devcontainer/docker-compose.yml | 2 ++ Dockerfile | 49 +++++++++++++++++++------------- python_starter/test_sample.py | 5 ++++ tests/test_sample.py | 2 ++ 5 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 python_starter/test_sample.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 360e6ae..bd03d50 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,10 @@ "name": "Existing Docker Compose (Extend)", // Update the 'dockerComposeFile' list if you have more compose files or use different names. - // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. + // The .devcontainer/docker-compose.yml file contains any overrides you + // need/want to make. It has been commented out because the project's + // compose.yml works as is. It's left here as something to be able to use + // later if you need it. "dockerComposeFile": [ "../compose.yml" // "docker-compose.yml" diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 6082339..bbb4cdb 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,3 +1,5 @@ +# This is not used in python-starter. It will be used if the +# ".docker-compose.yml" line is uncommented in devcontainer.json version: '3.8' services: # Update this to the name of the service you want to work with in your docker-compose.yml file diff --git a/Dockerfile b/Dockerfile index 76d680b..686847b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,8 @@ # I did not recommed using an alpine image because it lacks the package installer pip and the support for installing # wheel packages, which are both needed for installing applications like Pandas and Numpy. -# The base layer will contain the dependencies shared by the other layers +# The base layer contains the instruction for creating the app user, setting the +# working directory, setting an "always on" command. FROM python:3.11-slim-bookworm as base # Allowing the argumenets to be read into the dockerfile. Ex: .env > compose.yml > Dockerfile @@ -17,8 +18,7 @@ ARG UID=1000 ARG GID=1000 -# Create our users here in the last layer or else it will be lost in the previous discarded layers -# Create a system group named "app_user" with the -r flag +# Create the user and usergroup RUN groupadd -g ${GID} -o app RUN useradd -m -d /app -u ${UID} -g ${GID} -o -s /bin/bash app @@ -33,8 +33,10 @@ FROM base as poetry RUN pip install poetry==${POETRY_VERSION} -# Use this page as a reference for python and poetry environment variables: https://docs.python.org/3/using/cmdline.html#envvar-PYTHONUNBUFFERED -# Ensure the stdout and stderr streams are sent straight to terminal, then you can see the output of your application +# Use this page as a reference for python and poetry environment variables: +# https://docs.python.org/3/using/cmdline.html#envvar-PYTHONUNBUFFERED Ensure +# the stdout and stderr streams are sent straight to terminal, then you can see +# the output of your application ENV PYTHONUNBUFFERED=1\ # Avoid the generation of .pyc files during package install # Disable pip's cache, then reduce the size of the image @@ -48,29 +50,35 @@ ENV PYTHONUNBUFFERED=1\ POETRY_VIRTUALENVS_IN_PROJECT=1 \ POETRY_CACHE_DIR=/tmp/poetry_cache -FROM poetry as build -# Just copy the files needed to install the dependencies -COPY pyproject.toml poetry.lock README.md ./ - -# Poetry cache is used to avoid installing the dependencies every time the code changes, we will keep this folder in development environment and remove it in production -# --no-root, poetry will install only the dependencies avoiding to install the project itself, we will install the project in the final layer -# --without dev to avoid installing dev dependencies, we do not need test and linters in production environment -# --with dev to install dev dependencies, we need test and linters in development environment -# --mount, mount a folder for plugins with poetry cache, this will speed up the process of building the image -RUN poetry install --no-root --without dev && rm -rf ${POETRY_CACHE_DIR}; - # We want poetry on in development FROM poetry as development # Switch to the non-root user "user" USER app -# We don't want poetry on in production, so we copy the needed files form the build stage +# Below are production steps +FROM poetry as build + +# Only copy the files needed to install the dependencies. Poetry requires +# README.md to exist in order to work +COPY pyproject.toml poetry.lock README.md ./ + +# Install the depdencies with Poetry. +# +# --no-root is used so that poetry will install only the dependencies not the project itself +# +# --without dev to avoid installing dev dependencies +# +# Poetry cache is used to avoid installing the dependencies every time the code +# changes. We delete this folder after installing. +RUN poetry install --no-root --without dev && rm -rf ${POETRY_CACHE_DIR}; + +# We do not need poetry in production. We will copy dependencies from the build +# step. FROM base as production -# Switch to the non-root user "user" RUN mkdir -p /venv && chown ${UID}:${GID} /venv -# By adding /venv/bin to the PATH the dependencies in the virtual environment +# By adding /venv/bin to the PATH, the dependencies in the virtual environment # are used ENV VIRTUAL_ENV=/venv \ PATH="/venv/bin:$PATH" @@ -78,4 +86,5 @@ ENV VIRTUAL_ENV=/venv \ COPY --chown=${UID}:${GID} . /app COPY --chown=${UID}:${GID} --from=build "/app/.venv" ${VIRTUAL_ENV} -USER app +# Switch to the app user +USER app \ No newline at end of file diff --git a/python_starter/test_sample.py b/python_starter/test_sample.py new file mode 100644 index 0000000..78e3000 --- /dev/null +++ b/python_starter/test_sample.py @@ -0,0 +1,5 @@ +# Remove this file if you want tests in a separate tests directory +from python_starter.sample import Sample + +def test_add_one(): + assert Sample().add_one(21) == 22 \ No newline at end of file diff --git a/tests/test_sample.py b/tests/test_sample.py index d268941..e1412a7 100644 --- a/tests/test_sample.py +++ b/tests/test_sample.py @@ -1,3 +1,5 @@ +# Remove this tests directory if you want to have tests inline with production +# code from python_starter.sample import Sample def test_add_one():