Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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. 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"
],

// 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": {},
"ghcr.io/devcontainers/features/sshd: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"
}
28 changes: 28 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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
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"

1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.venv
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@
.bash_history
.python_history
.cache/
.pytest_cache
__pycache__
.dotnet/
.vscode-server
.local
.gitconfig
.venv
.ssh
86 changes: 47 additions & 39 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,35 @@
# 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
ARG POETRY_VERSION
# true = development / false = production
ARG DEV
ARG UID=1000
ARG GID=1000


# 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


# Set the working directory to /app
WORKDIR /app

# 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
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}

# 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
Expand All @@ -34,49 +50,41 @@ ENV PYTHONUNBUFFERED=1\
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache

RUN pip install poetry==${POETRY_VERSION}
# We want poetry on in development
FROM poetry as development

# Install the app. Just copy the files needed to install the dependencies
COPY pyproject.toml poetry.lock README.md ./
# Switch to the non-root user "user"
USER app

# 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

# Set up our final runtime layer
FROM python:3.11-slim-bookworm as runtime
# Below are production steps
FROM poetry as build

ARG UID=1000
ARG GID=1000
# 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 ./

# 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
# 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
RUN mkdir -p /venv && chown ${UID}:${GID} /venv
RUN which pip && sleep 10

# 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"

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
COPY --chown=${UID}:${GID} --from=build "/app/.venv" ${VIRTUAL_ENV}

CMD ["tail", "-f", "/dev/null"]
# Switch to the app user
USER app
2 changes: 1 addition & 1 deletion compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ services:
app:
build:
context: .
target: runtime
target: development
dockerfile: Dockerfile
args:
UID: ${UID:-1000}
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "python-starter"
version = "0.1.0"
description = "Boilerplate code for developing a python app in docker"
authors = ["Monique Rio <mrio@umich.edu>, Samuel Sciolla <ssciolla@umich.edu>, Lianet Sepulveda Torres <lisepul@umich.edu>, "]
authors = [ "Monique Rio <mrio@umich.edu>, Samuel Sciolla <ssciolla@umich.edu>, Lianet Sepulveda Torres <lisepul@umich.edu>"]
readme = "README.md"
packages = [{include = "python_starter"}]

Expand Down
Empty file added python_starter/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions python_starter/sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Sample:
def __init__(self):
pass

def add_one(self, a):
return(a + 1)
5 changes: 5 additions & 0 deletions python_starter/test_sample.py
Original file line number Diff line number Diff line change
@@ -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
Empty file added tests/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions tests/test_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Remove this tests directory if you want to have tests inline with production
# code
from python_starter.sample import Sample

def test_add_one():
assert Sample().add_one(21) == 22