diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..08f4aac9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,123 @@ +# syntax=docker/dockerfile:1 +# Keep this syntax directive! It's used to enable Docker BuildKit + +################################ +# PYTHON-BASE +# Sets up all our shared environment variables +################################ +FROM python:3.11.7-slim as python-base + + # Python +ENV PYTHONUNBUFFERED=1 \ + # pip + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=100 \ + \ + # Poetry + # https://python-poetry.org/docs/configuration/#using-environment-variables + POETRY_VERSION=1.7.1 \ + # make poetry install to this location + POETRY_HOME="/opt/poetry" \ + # do not ask any interactive question + POETRY_NO_INTERACTION=1 \ + # never create virtual environment automaticly, only use env prepared by us + POETRY_VIRTUALENVS_CREATE=false \ + # this is where our requirements + virtual environment will live + VIRTUAL_ENV="/venv" + +# prepend poetry and venv to path +ENV PATH="$POETRY_HOME/bin:$VIRTUAL_ENV/bin:$PATH" + +# prepare virtual env +RUN python -m venv $VIRTUAL_ENV + +# working directory and Python path +WORKDIR /app +ENV PYTHONPATH="/app:$PYTHONPATH" + +################################ +# BUILDER-BASE +# Used to build deps + create our virtual environment +################################ +FROM python-base as builder-base +RUN apt-get update && \ + apt-get install -y \ + apt-transport-https \ + gnupg \ + ca-certificates \ + build-essential \ + git \ + vim \ + curl + +# install poetry - respects $POETRY_VERSION & $POETRY_HOME +# The --mount will mount the buildx cache directory to where +# Poetry and Pip store their cache so that they can re-use it +RUN --mount=type=cache,target=/root/.cache \ + curl -sSL https://install.python-poetry.org | python - + +# used to init dependencies +WORKDIR /app +COPY pyproject.toml ./ +RUN poetry lock + +# install runtime deps to VIRTUAL_ENV +RUN --mount=type=cache,target=/root/.cache \ + poetry install --no-root --all-extras --only main + + +################################ +# DEVELOPMENT +# Image used during development / testing +################################ +FROM builder-base as development + +WORKDIR /app +COPY --from=builder-base /app/pyproject.toml pyproject.toml +COPY --from=builder-base /app/poetry.lock poetry.lock + + +# quicker install as runtime deps are already installed +RUN --mount=type=cache,target=/root/.cache \ + poetry install --no-root --all-extras --with dev + +COPY . . +RUN poetry install --all-extras --only-root + + +EXPOSE 8000 +CMD ["bash"] + + +################################ +# PRODUCTION +# Final image used for runtime +################################ +FROM python-base as production + +ENV WORKER_COUNT=1 + +RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates && \ + apt-get clean + +# copy in our built poetry + venv +COPY --from=builder-base $POETRY_HOME $POETRY_HOME +COPY --from=builder-base $VIRTUAL_ENV $VIRTUAL_ENV + +WORKDIR /app + +COPY --from=builder-base /app/pyproject.toml pyproject.toml +COPY --from=builder-base /app/poetry.lock poetry.lock + +COPY src/ src/ +COPY config/ config/ +RUN touch README.md +RUN poetry install --all-extras --only-root + +EXPOSE 8000 + +CMD ["sh", "-c", "gunicorn canopy_server.app:app --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers $WORKER_COUNT"] + + diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..56694de9 --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +IMAGE_NAME = canopy +DOCKERFILE_DIR = . +COMMON_BUILD_ARGS = --progress plain +EXTRA_BUILD_ARGS = +IMAGE_VERSION = $(shell poetry version -s) +PORT = 8000 +ENV_FILE = .env +TEST_WORKER_COUNT = 8 + +.PHONY: lint static test test-unit test-system test-e2e docker-build docker-build-dev docker-run docker-run-dev help + +lint: + poetry run flake8 . + +static: + poetry run mypy src + +test: + poetry run pytest -n $(TEST_WORKER_COUNT) --dist loadscope + +test-unit: + poetry run pytest -n $(TEST_WORKER_COUNT) --dist loadscope tests/unit + +test-system: + poetry run pytest -n $(TEST_WORKER_COUNT) --dist loadscope tests/system + +test-e2e: + poetry run pytest -n $(TEST_WORKER_COUNT) --dist loadscope tests/e2e + +docker-build: + @echo "Building Docker image..." + docker build $(COMMON_BUILD_ARGS) $(EXTRA_BUILD_ARGS) -t $(IMAGE_NAME):$(IMAGE_VERSION) $(DOCKERFILE_DIR) + @echo "Docker build complete." + +docker-build-dev: + @echo "Building Docker image for development..." + docker build $(COMMON_BUILD_ARGS) $(EXTRA_BUILD_ARGS) -t $(IMAGE_NAME)-dev:$(IMAGE_VERSION) --target=development $(DOCKERFILE_DIR) + @echo "Development Docker build complete." + +docker-run: + docker run --env-file $(ENV_FILE) -p $(PORT):$(PORT) $(IMAGE_NAME):$(IMAGE_VERSION) + +docker-run-dev: + docker run -it --env-file $(ENV_FILE) -p $(PORT):$(PORT) $(IMAGE_NAME)-dev:$(IMAGE_VERSION) + + +help: + @echo "Available targets:" + @echo "" + @echo " -- DEV -- " + @echo " make lint - Lint the code." + @echo " make static - Run static type checks." + @echo " make test - Test the code." + @echo " make test-unit - Run unit tests." + @echo " make test-system - Run system tests." + @echo " make test-e2e - Run e2e tests." + @echo "" + @echo " -- DOCKER -- " + @echo " make docker-build - Build the Docker image." + @echo " make docker-build-dev - Build the Docker image for development." + @echo " make docker-run - Run the Docker image." + @echo " make docker-run-dev - Run the Docker image for development."