From Zero to Hero
by Haseeb Majid
# Dockerfile
FROM python:3.9.8
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH="/app" \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
\
POETRY_VERSION=1.1.11 \
POETRY_HOME="/opt/poetry" \
POETRY_VIRTUALENVS_IN_PROJECT=true \
PYSETUP_PATH="/opt/pysetup" \
POETRY_NO_INTERACTION=1 \
\
VENV_PATH="/opt/pysetup/.venv"
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
WORKDIR $PYSETUP_PATH
COPY pyproject.toml poetry.lock ./
RUN pip install poetry==$POETRY_VERSION && \
poetry install
WORKDIR /app
COPY . .
CMD [ "bash", "/app/start.sh" ]
docker build --tag app .
docker run --publish 80:80 app
# Access app on http://localhost
sudo apt update
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql.service
sudo -u postgres createuser --interactive
sudo -u postgres createdb test
docker run --volume "postgres_data:/var/lib/postgresql/data" \
--environment "POSTGRES_DATABASE=postgres" \
--environment "POSTGRES_PASSWORD=postgres" \
--publish "5432:5432" \
postgres:13.4
# Start Commands:
docker network create --driver bridge workspace_network
docker volume create postgres_data
docker build -t app .
docker run --environment "POSTGRES_USER=postgres" \
--environment "POSTGRES_HOST=postgres" \
--environment "POSTGRES_DATABASE=postgres" \
--environment "POSTGRES_PASSWORD=postgres" \
--environment "POSTGRES_PORT=5432" \
--volume "./:/app" --publish "80:8080" \
--network workspace_network --name workspace_app \
--detach app
docker run --volume "postgres_data:/var/lib/postgresql/data" \
--environment "POSTGRES_DATABASE=postgres" \
--environment "POSTGRES_PASSWORD=postgres" \
--publish "5432:5432" --network workspace_network \
--name workspace_postgres --detach postgres:13.4
# Delete Commands:
docker stop workspace_app
docker rm workspace_app
docker stop workspace_postgres
docker rm workspace_postgres
docker network rm workspace_network
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
command: bash /app/start.sh --reload
volumes:
- ./:/app
environment:
- POSTGRES_USER=postgres
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_PORT=5432
ports:
- 127.0.0.1:80:80
postgres:
image: postgres:13.4
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DATABASE=postgres
- POSTGRES_PASSWORD=postgres
ports:
- 127.0.0.1:5432:5432
docker compose up --build
docker compose down
docker compose run app pytest
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
depends_on:
- postgres
# ...
postgres:
# ...
# .github/workflows/branch.yml
name: Check changes on branch
on:
push:
branches:
- "*"
- "!main"
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13.4
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.9
uses: actions/setup-python@v3
with:
python-version: 3.9
- name: Install dependencies
run: |
pip install poetry=1.11.0
poetry install
- name: Test with pytest
run: |
export DB_USERNAME=postgres
export DB_PASSWORD=postgres
export DB_HOST=postgres
export DB_PORT=5432
export DB_NAME=postgres
pytest
# .github/workflows/branch.yml
name: Check changes on branch
#...
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- name: Run Tests
run: docker compose run app pytest
# Dockerfile
FROM python:3.9.8-slim
# ...
WORKDIR $PYSETUP_PATH
COPY pyproject.toml poetry.lock ./
RUN pip install poetry==$POETRY_VERSION && \
poetry install
WORKDIR /app
COPY . .
CMD [ "bash", "/app/start.sh" ]
python:3.9.8 | python:3.9.8-slim | |
---|---|---|
Size | 1 GB | 280 MB |
Build[1] | 75 sec | 30 sec |
CI Pipeline Job | 2 min 40 sec | 1 min 57 sec |
# Dockerfile
FROM python:3.9.8-slim as base
ARG PYSETUP_PATH
ENV PYTHONPATH="/app"
ENV PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
\
POETRY_VERSION=1.1.11 \
POETRY_HOME="/opt/poetry" \
POETRY_VIRTUALENVS_IN_PROJECT=true \
PYSETUP_PATH="/opt/pysetup" \
POETRY_NO_INTERACTION=1 \
\
VENV_PATH="/opt/pysetup/.venv"
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
FROM base as builder
RUN pip install poetry==$POETRY_VERSION
WORKDIR $PYSETUP_PATH
COPY poetry.lock pyproject.toml ./
RUN poetry install --no-dev
FROM builder as development
RUN poetry install
WORKDIR /app
COPY . .
EXPOSE 80
CMD ["bash", "/app/start.sh", "--reload"]
FROM base as production
COPY --from=builder $VENV_PATH $VENV_PATH
WORKDIR /app
COPY . .
EXPOSE 80
CMD ["bash", "/app/start.sh"]
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
target: development
command: bash /app/start.sh --reload
depends_on:
- postgres
environment:
- # ...
volumes:
- ./:/app
ports:
- 127.0.0.1:80:80
python:3.9.8-slim | Multistage[2] | |
---|---|---|
Size | 280 MB | 200 MB |
Build[1] | 30 Seconds | 35 seconds |
# docker-compose.yml
services:
app:
build:
context: .
target: development
cache_from:
- registry.gitlab.com/haseeb-slides/developing-with-docker-slides/python-image:latest
command: bash /app/start.sh --reload
# ....
poetry add [email protected]:zoe/pubsub.git
[tool.poetry.dependencies]
python = "^3.9"
fastapi = "^0.70.0"
pubsub = { git = "ssh://[email protected]:zoe/pubsub.git",
rev = "0.2.5" }
psycopg2-binary = "^2.9.3"
SQLAlchemy = "^1.4.36"
uvicorn = "^0.17.6"
FROM base as builder
RUN apt-get update && \
apt-get install openssh-client git -y && \
mkdir -p -m 0600 \
~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts && \
pip install poetry==$POETRY_VERSION
WORKDIR $PYSETUP_PATH
COPY poetry.lock pyproject.toml ./
RUN --mount=type=ssh poetry install --no-dev
First add our ssh key
ssh-add ~/.ssh/id_rsa
Then we can do
docker compose build --ssh default
# .github/workflows/branch.yml
jobs:
# ...
test:
# ...
steps:
- uses: actions/checkout@v3
- uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.PRIVATE_SSH_KEY }}
- name: Build Image
run: docker compose build --ssh default
- name: Run Tests
run: docker compose run app pytest
python:3.9.8-slim[2] | Multistage[3] | |
---|---|---|
Size | 400 MB | 200 MB |
Build[1] | 39 Seconds | 46 seconds |