Skip to content

Commit 0fca04b

Browse files
authored
Remove builder-base and development image stages, run linting and tests outside Docker (#111)
* Remove `builder-base` and `development` image stages Now, we only have two Dockerfile stages: - `base`, where we install dependencies with poetry - `production`, where we copy the source files and run the app We use environment variables to run the app in a "nonprod" or "prod" mode We also add a .dockerignore file to limit the files that make it into the production container Small fixes: * Make docker entrypoint executable * Move gunicorn_conf.py to config dir * Refactor Make rules - add `docker-` prefix for shell and start rules - add a local start rule - modify help text - Run lint, format, and test outside of Docker
1 parent 28baf6d commit 0fca04b

File tree

9 files changed

+78
-110
lines changed

9 files changed

+78
-110
lines changed

.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ignore everything
2+
*
3+
# use exceptions to create an "allow list"
4+
!/config
5+
!/src
6+
!/bin
7+
!/poetry.lock
8+
!/pyproject.toml
9+
!/version.json

.secrets.baseline

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,16 @@
115115
"filename": "config/local_dev.env",
116116
"hashed_secret": "4b9a4ce92b6a01a4cd6ee1672d31c043f2ae79ab",
117117
"is_verified": false,
118-
"line_number": 5
118+
"line_number": 7
119119
},
120120
{
121121
"type": "Secret Keyword",
122122
"filename": "config/local_dev.env",
123123
"hashed_secret": "77ea6398f252999314d609a708842a49fc43e055",
124124
"is_verified": false,
125-
"line_number": 8
125+
"line_number": 10
126126
}
127127
]
128128
},
129-
"generated_at": "2022-07-13T09:34:14Z"
129+
"generated_at": "2022-07-14T17:13:15Z"
130130
}

Dockerfile

Lines changed: 23 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,46 @@
11
# Creating a python base with shared environment variables
2-
FROM python:3.10.5-slim as python-base
3-
ENV PYTHONPATH=/app \
4-
PYTHONUNBUFFERED=1 \
5-
PYTHONDONTWRITEBYTECODE=1 \
6-
PIP_NO_CACHE_DIR=off \
7-
PIP_DISABLE_PIP_VERSION_CHECK=on \
2+
FROM python:3.10.5 as base
3+
ENV PIP_NO_CACHE_DIR=off \
84
PIP_DEFAULT_TIMEOUT=100 \
5+
PIP_DISABLE_PIP_VERSION_CHECK=on \
96
POETRY_HOME="/opt/poetry" \
10-
POETRY_VIRTUALENVS_IN_PROJECT=true \
117
POETRY_NO_INTERACTION=1 \
12-
PYSETUP_PATH="/opt/pysetup" \
13-
VENV_PATH="/opt/pysetup/.venv" \
14-
PORT=8000
15-
16-
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
17-
18-
# Set up user and group
19-
ARG userid=10001
20-
ARG groupid=10001
21-
WORKDIR /app
22-
RUN groupadd --gid $groupid app && \
23-
useradd -g app --uid $userid --shell /usr/sbin/nologin --create-home app
24-
25-
RUN mkdir -p $POETRY_HOME && \
26-
chown app:app /opt/poetry && \
27-
mkdir -p $PYSETUP_PATH && \
28-
chown app:app $PYSETUP_PATH && \
29-
mkdir -p /app && \
30-
chown app:app /app
31-
32-
RUN apt-get update && \
33-
apt-get install --assume-yes apt-utils && \
34-
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
8+
POETRY_VIRTUALENVS_IN_PROJECT=true \
9+
PYSETUP_PATH="/opt/pysetup"
3510

36-
# builder-base is used to build dependencies
37-
FROM python-base as builder-base
38-
RUN apt-get install --no-install-recommends -y \
39-
curl \
40-
build-essential
11+
ENV PATH="$POETRY_HOME/bin:$PATH"
4112

4213
# Install Poetry - respects $POETRY_VERSION & $POETRY_HOME
43-
USER app
4414
ENV POETRY_VERSION=1.1.14
4515
RUN curl -sSL https://install.python-poetry.org | python3 -
4616

4717
# We copy our Python requirements here to cache them
4818
# and install only runtime deps using poetry
4919
WORKDIR $PYSETUP_PATH
50-
COPY --chown=app:app ./poetry.lock ./pyproject.toml ./
20+
COPY ./poetry.lock ./pyproject.toml ./
5121
RUN poetry install --no-dev --no-root
5222

23+
# `production` stage uses the dependencies downloaded in the `base` stage
24+
FROM python:3.10.5-slim as production
25+
ENV PROMETHEUS_MULTIPROC=1 \
26+
PORT=8000 \
27+
PYTHONUNBUFFERED=1 \
28+
PYTHONDONTWRITEBYTECODE=1 \
29+
VIRTUAL_ENV=/opt/pysetup/.venv
5330

54-
# 'development' stage installs all dev deps and can be used to develop code.
55-
# For example using docker-compose to mount local volume under /app
56-
FROM python-base as development
57-
# to run detect-secrets
58-
RUN apt-get install --no-install-recommends -y git
31+
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
5932

60-
ENV FASTAPI_ENV=development
33+
COPY --from=base $VIRTUAL_ENV $VIRTUAL_ENV
6134

62-
# Copying poetry and venv into image
35+
ARG userid=10001
36+
ARG groupid=10001
37+
RUN groupadd --gid $groupid app && \
38+
useradd -g app --uid $userid --shell /usr/sbin/nologin --create-home app
6339
USER app
64-
COPY --from=builder-base $POETRY_HOME $POETRY_HOME
65-
COPY --from=builder-base $PYSETUP_PATH $PYSETUP_PATH
66-
67-
# Copying in our entrypoint
68-
COPY --chown=app:app ./bin/docker-entrypoint.sh /docker-entrypoint.sh
69-
RUN chmod +x /docker-entrypoint.sh
70-
71-
# venv already has runtime deps installed we get a quicker install
72-
WORKDIR $PYSETUP_PATH
73-
RUN poetry install --no-root
74-
75-
WORKDIR /app
76-
COPY --chown=app:app . .
77-
78-
EXPOSE $PORT
79-
ENTRYPOINT ["/docker-entrypoint.sh"]
80-
CMD uvicorn src.app.api:app --reload --host=0.0.0.0 --port=$PORT
81-
82-
83-
# 'production' stage uses the clean 'python-base' stage and copyies
84-
# in only our runtime deps that were installed in the 'builder-base'
85-
FROM python-base as production
86-
ENV FASTAPI_ENV=production \
87-
IS_GUNICORN=1 \
88-
PROMETHEUS_MULTIPROC=1
89-
90-
COPY --from=builder-base $VENV_PATH $VENV_PATH
91-
COPY ./bin/gunicorn_conf.py /gunicorn_conf.py
92-
93-
COPY ./bin/docker-entrypoint.sh /docker-entrypoint.sh
94-
RUN chmod +x /docker-entrypoint.sh
9540

96-
COPY . /app
9741
WORKDIR /app
42+
COPY . .
9843

9944
EXPOSE $PORT
100-
ENTRYPOINT ["/docker-entrypoint.sh"]
101-
CMD gunicorn -k uvicorn.workers.UvicornWorker -c /gunicorn_conf.py src.app.api:app
45+
ENTRYPOINT ["bin/docker-entrypoint.sh"]
46+
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "config/gunicorn_conf.py", "src.app.api:app"]

Makefile

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,61 @@
44
_UID ?= 10001
55
_GID ?= 10001
66

7+
VENV := $(shell echo $${VIRTUAL_ENV-.venv})
8+
INSTALL_STAMP = $(VENV)/.install.stamp
9+
POETRY_VIRTUALENVS_IN_PROJECT = true
10+
711
.PHONY: help
812
help:
913
@echo "Usage: make RULE"
1014
@echo ""
1115
@echo "JBI make rules:"
1216
@echo ""
13-
@echo " build - build docker containers"
14-
@echo " lint - lint check for code"
15-
@echo " format - run formatters (black, isort), fix in place"
16-
@echo " start - run the API service"
17+
@echo "Local"
18+
@echo " format - run formatters (black, isort), fix in place"
19+
@echo " lint - run linters"
20+
@echo " start - run the API service locally"
21+
@echo " test - run test suite"
1722
@echo ""
18-
@echo " test - run test suite"
19-
@echo " shell - open a shell in the web container"
23+
@echo "Docker"
24+
@echo " build - build docker container"
25+
@echo " docker-start - run the API service through docker"
26+
@echo " docker-shell - open a shell in the web container"
2027
@echo ""
21-
@echo " help - see this text"
28+
@echo " help - see this text"
2229

30+
install: $(INSTALL_STAMP)
31+
$(INSTALL_STAMP): poetry.lock
32+
@if [ -z $(shell command -v poetry 2> /dev/null) ]; then echo "Poetry could not be found. See https://python-poetry.org/docs/"; exit 2; fi
33+
poetry install --no-root
34+
touch $(INSTALL_STAMP)
2335

2436
.PHONY: build
2537
build:
2638
docker-compose build \
2739
--build-arg userid=${_UID} --build-arg groupid=${_GID}
2840

2941
.PHONY: format
30-
format:
42+
format: $(INSTALL_STAMP)
3143
bin/lint.sh black --fix
3244
bin/lint.sh isort --fix
3345

3446
.PHONY: lint
35-
lint:
36-
docker-compose run --rm web bin/lint.sh
37-
38-
.PHONY: shell
39-
shell:
40-
docker-compose run web /bin/sh
47+
lint: $(INSTALL_STAMP)
48+
bin/lint.sh
4149

4250
.PHONY: start
4351
start:
52+
poetry run python -m src.app.api
53+
54+
.PHONY: docker-shell
55+
docker-shell:
56+
docker-compose run --rm web /bin/sh
57+
58+
.PHONY: docker-start
59+
docker-start:
4460
docker-compose up
4561

4662
.PHONY: test
47-
test:
48-
docker-compose run --rm web bin/test.sh
63+
test: $(INSTALL_STAMP)
64+
bin/test.sh

bin/docker-entrypoint.sh

100644100755
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
set -e
44

5-
# activate our virtual environment here
6-
. /opt/pysetup/.venv/bin/activate
7-
85
# setup an empty directory for Prometheus metrics
96
if [ ${PROMETHEUS_MULTIPROC:-0} -eq 1 ]; then
107
prometheus_multiproc_dir=`mktemp -td prometheus.XXXXXXXXXX` || exit 1
File renamed without changes.

config/local_dev.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Environment variables for the local development environment
22

3+
ENV=nonprod
4+
35
# Jira API Secrets
46
JIRA_USERNAME="fake_jira_username"
57
JIRA_API_KEY="fake_jira_api_key"

docker-compose.yaml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
version: "3.8"
22
services:
33
web:
4-
build:
5-
context: .
6-
target: development
7-
volumes:
8-
- .:/app
9-
ports:
10-
- ${PORT:-8000}:${PORT:-8000}
4+
build: .
5+
command: python -m src.app.api
6+
env_file:
7+
- config/local_dev.env
118
# Let the init system handle signals for us.
129
# among other things this helps shutdown be fast
1310
init: true
14-
env_file:
15-
- config/local_dev.env
11+
ports:
12+
- ${PORT:-8000}:${PORT:-8000}
13+
volumes:
14+
- .:/app

tests/unit/test_gunicorn_conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
from pydantic import ValidationError
33

4-
from bin.gunicorn_conf import GunicornSettings
4+
from config.gunicorn_conf import GunicornSettings
55

66

77
def test_set_bind(monkeypatch):

0 commit comments

Comments
 (0)