Skip to content

Commit a24c705

Browse files
authored
Merge pull request #1185 from PolicyEngine/pek-docker-compose-v2
Add docker compose support, make documentation
2 parents 5a04805 + 33d672e commit a24c705

11 files changed

Lines changed: 224 additions & 11 deletions

.dockerignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ logs/
7575
# Temporary files
7676
tmp/
7777
temp/
78+
79+
# Docker
80+
docker/Dockerfile*
81+
docker/docker-compose.yml

.env-example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FLASK_DEBUG=1
2+
CACHE_REDIS_HOST=redis

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
"[python]": {
33
"editor.defaultFormatter": "ms-python.black-formatter"
44
},
5-
"python.formatting.provider": "none"
5+
"python.formatting.provider": "none",
6+
"python-envs.defaultEnvManager": "ms-python.python:conda",
7+
"python-envs.defaultPackageManager": "ms-python.python:conda",
8+
"python-envs.pythonProjects": []
69
}

Makefile

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
install:
1+
.PHONY: help
2+
help: ## Print this message
3+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-24s\033[0m %s\n", $$1, $$2}'
4+
5+
install: ## Install Python dependencies
26
pip install -U -e .[dev]
37

4-
debug:
5-
FLASK_APP=policyengine_household_api.api FLASK_DEBUG=1 flask run --without-threads
8+
debug: ## Run Flask app with FLASK_DEBUG=1
9+
FLASK_APP=policyengine_household_api.api FLASK_DEBUG=1 flask run --without-threads --host=0.0.0.0
610

7-
test:
11+
test: ## Run unit tests
812
pytest -vv --timeout=150 -rP tests/to_refactor tests/unit
913

10-
test-with-auth:
14+
test-with-auth: ## Run integration tests
1115
CONFIG_FILE=config/test_with_auth.yaml pytest -vv --timeout=150 -rP tests/integration_with_auth
1216

13-
debug-test:
17+
debug-test: ## Run tests with FLASK_DEBUG=1
1418
FLASK_DEBUG=1 pytest -vv --durations=0 --timeout=150 -rP tests
1519

16-
format:
20+
format: ## Run black
1721
black . -l 79
1822

19-
deploy:
23+
deploy: ## Deploy to GCP
2024
python gcp/export.py
2125
gcloud config set app/cloud_build_timeout 1800
2226
cp gcp/policyengine_household_api/* .
@@ -25,9 +29,56 @@ deploy:
2529
rm Dockerfile
2630
rm .gac.json
2731

28-
changelog:
32+
changelog: ## Build changelog
2933
build-changelog changelog.yaml --output changelog.yaml --update-last-date --start-from 0.1.0 --append-file changelog_entry.yaml
3034
build-changelog changelog.yaml --org PolicyEngine --repo policyengine-household-api --output CHANGELOG.md --template .github/changelog_template.md
3135
bump-version changelog.yaml setup.py policyengine_household_api/constants.py
3236
rm changelog_entry.yaml || true
33-
touch changelog_entry.yaml
37+
touch changelog_entry.yaml
38+
39+
COMPOSE_FILE ?= docker/docker-compose.yml
40+
COMPOSE_EXTERNAL_FILE ?= docker/docker-compose.external.yml
41+
DOCKER_IMG ?= policyengine:policyengine-household-api
42+
DOCKER_NAME ?= policyengine-household-api
43+
ifeq (, $(shell which docker))
44+
DOCKER_CONTAINER_ID := docker-is-not-installed
45+
else
46+
DOCKER_CONTAINER_ID := $(shell docker ps --filter ancestor=$(DOCKER_IMG) --format "{{.ID}}")
47+
endif
48+
DOCKER_NETWORK ?= policyengine-api_default
49+
DOCKER_CONSOLE ?= policyengine-api-console
50+
51+
.PHONY: docker-build
52+
docker-build: ## Build the docker image
53+
docker compose --file $(COMPOSE_FILE) build --force-rm
54+
55+
.PHONY: docker-run
56+
docker-run: ## Run the app as docker container with supporting services
57+
docker compose --file $(COMPOSE_FILE) up
58+
59+
.PHONY: docker-run-external
60+
docker-run-external: ## Run with external network (for multi-service setups)
61+
docker compose --file $(COMPOSE_FILE) --file $(COMPOSE_EXTERNAL_FILE) up
62+
63+
.PHONY: services-start
64+
services-start: ## Run the docker containers for supporting services (e.g. Redis)
65+
docker compose --file $(COMPOSE_FILE) up -d redis
66+
67+
.PHONY: services-start-external
68+
services-start-external: ## Start services with external network
69+
docker compose --file $(COMPOSE_FILE) --file $(COMPOSE_EXTERNAL_FILE) up -d redis
70+
71+
.PHONY: services-stop
72+
services-stop: ## Stop the docker containers for supporting services
73+
docker compose --file $(COMPOSE_FILE) down
74+
75+
.PHONY: docker-network-create
76+
docker-network-create: ## Create the external Docker network (for multi-service setups)
77+
docker network create $(DOCKER_NETWORK) || true
78+
79+
.PHONY: docker-console
80+
docker-console: ## Open a one-off container bash session
81+
@docker run -p 8080:5000 -v $(PWD):/code \
82+
--network $(DOCKER_NETWORK) \
83+
--rm --name $(DOCKER_CONSOLE) -it \
84+
$(DOCKER_IMG) bash

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,54 @@
22

33
A version of the PolicyEngine API that runs the `calculate` endpoint over household object. To debug locally, run `make debug`.
44

5+
## Local development with Docker Compose
6+
7+
To run this app locally via Docker Compose:
8+
9+
```
10+
% make docker-build
11+
% make docker-run
12+
```
13+
14+
and point your browser at http://localhost:8080 to access the API.
15+
16+
To develop the code locally, you will want to instead start only the Redis docker container and a one-off
17+
API container, with your local filesystem mounted into the running docker container.
18+
19+
```
20+
% make services-start
21+
% make docker-console
22+
```
23+
24+
Then inside the container, start the Flask service:
25+
26+
```
27+
policyapi@[your-docker-id]:/code$ make debug
28+
```
29+
30+
and point your browser at http://localhost:8080 to access the API.
31+
32+
### Running with other PolicyEngine services
33+
34+
If you're running this alongside other PolicyEngine services (e.g., the main API) and need
35+
containers to communicate across projects, use the external network mode:
36+
37+
```
38+
% make docker-network-create # Create shared network (once)
39+
% make docker-run-external # Run with external network
40+
```
41+
42+
This connects the household API to a shared `policyengine-api_default` network that other
43+
PolicyEngine docker-compose projects can also join.
44+
45+
For development with external networking:
46+
47+
```
48+
% make docker-network-create
49+
% make services-start-external
50+
% make docker-console
51+
```
52+
553
## Development rules
654

755
1. Every endpoint should return a JSON object with at least a "status" and "message" field.

changelog_entry.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- bump: minor
2+
changes:
3+
added:
4+
- Docker Compose support for local development with Redis
5+
- Makefile targets for building and running Docker containers
6+
- Support for external Docker networks when running alongside other PolicyEngine services

config/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ services:
9595
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
9696
```
9797
98+
Or, alternately, use the `docker/docker-compose.yml` file and create a `.env` file in your root directory
99+
to set all environment variables. Example:
100+
101+
```
102+
% cat .env
103+
CONFIG_FILE=/code/config/custom.yaml
104+
DATABASE__PASSWORD=your-secret-password
105+
ANTHROPIC_API_KEY=your-key-here
106+
```
107+
98108
#### Kubernetes ConfigMap
99109
```yaml
100110
apiVersion: v1

docker/Dockerfile.api

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
FROM python:3.12
2+
3+
# use root to install pip libs
4+
USER root
5+
6+
WORKDIR /code
7+
8+
COPY . /code/
9+
10+
ENV PYTHONDONTWRITEBYTECODE 1
11+
ENV PYTHONUNBUFFERED 1
12+
13+
# can't use make install because we ignore .github/ via .dockerignore
14+
# and env is set via docker-compose.yml or injection at container run time
15+
RUN pip install -e ".[dev]" --config-settings editable_mode=compat
16+
17+
# switch to app user
18+
RUN groupadd policyapi && \
19+
useradd -g policyapi policyapi && \
20+
apt-get purge -y --auto-remove build-essential && \
21+
apt-get -y install make && \
22+
chown -R policyapi:policyapi /code
23+
24+
RUN chown -R policyapi:policyapi /usr/local/lib/python3.12/site-packages/policyengine*
25+
26+
USER policyapi

docker/docker-compose.external.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Override file for connecting to an external Docker network.
2+
# Use this when running alongside other PolicyEngine services.
3+
#
4+
# Usage:
5+
# make docker-run-external
6+
#
7+
# Or manually:
8+
# docker compose -f docker/docker-compose.yml -f docker/docker-compose.external.yml up
9+
#
10+
# Prerequisites:
11+
# The external network must exist. Create it with:
12+
# docker network create policyengine-api_default
13+
14+
networks:
15+
my_network:
16+
name: ${DOCKER_NETWORK:-policyengine-api_default}
17+
external: true

docker/docker-compose.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
services:
2+
redis:
3+
image: "redis:alpine"
4+
expose:
5+
- "6379"
6+
volumes:
7+
- redis-data:/data
8+
networks:
9+
- my_network
10+
11+
policyengine:
12+
build:
13+
context: ../
14+
dockerfile: docker/Dockerfile.api
15+
command: ["/bin/bash", "/code/docker/start.sh"]
16+
image: policyengine:policyengine-household-api
17+
depends_on:
18+
- redis
19+
env_file:
20+
- ../.env
21+
expose:
22+
- 8080
23+
ports:
24+
- "8080:8080"
25+
networks:
26+
- my_network
27+
28+
volumes:
29+
redis-data:
30+
31+
networks:
32+
my_network:
33+
name: ${DOCKER_NETWORK:-policyengine-api_default}

0 commit comments

Comments
 (0)