Skip to content

Commit 370273e

Browse files
committed
ci: ci cd to build and push image
1 parent 590aa06 commit 370273e

8 files changed

Lines changed: 470 additions & 5 deletions

File tree

.dockerignore

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Version control
2+
.git
3+
.gitignore
4+
.github
5+
6+
# Python cache and bytecode
7+
__pycache__/
8+
*.py[cod]
9+
*$py.class
10+
*.so
11+
.Python
12+
*.egg
13+
*.egg-info
14+
dist/
15+
build/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
*.manifest
25+
*.spec
26+
.pytest_cache/
27+
.mypy_cache/
28+
.ruff_cache/
29+
.coverage
30+
.coverage.*
31+
htmlcov/
32+
33+
# Virtual environments
34+
env/
35+
venv/
36+
ENV/
37+
.venv
38+
pipenv/
39+
40+
# Environment files and secrets
41+
.env
42+
.env.*
43+
!.env.example
44+
45+
# Development and analysis files
46+
*.ipynb
47+
.ipynb_checkpoints/
48+
notebooks/
49+
demo_*.ipynb
50+
51+
# Documentation
52+
docs/
53+
build_docs.py
54+
specs/
55+
*.md
56+
!README.md
57+
58+
# Tests
59+
tests/
60+
pytest.ini
61+
.pytest_cache/
62+
63+
# IDE and editors
64+
.vscode/
65+
.idea/
66+
*.swp
67+
*.swo
68+
*.swn
69+
.DS_Store
70+
Thumbs.db
71+
72+
# Data and outputs
73+
data/output/
74+
*.csv
75+
*.json
76+
!kpis.json
77+
!measures.json
78+
!living_labs_data.json
79+
!kpi_groups.json
80+
!kpi_groups_mcda_goals.json
81+
82+
# Build and package files
83+
*.whl
84+
*.tar.gz
85+
86+
# Other
87+
*.log
88+
.pipenv/
89+
Pipfile
90+
Pipfile.lock
91+
LICENSE
92+
imact-analysis-queries/

.github/workflows/docker-build.yml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Build and Push Docker image to GitLab Registry
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: read
8+
packages: read
9+
10+
env:
11+
IMAGE_NAME: inocs-sum-odp-impact-api
12+
REGISTRY: ${{ secrets.GITLAB_REGISTRY }}
13+
PROJECT_PATH: ${{ vars.GITLAB_PROJECT_PATH }}
14+
15+
jobs:
16+
test:
17+
name: Run Tests
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: "3.13"
28+
29+
- name: Install pipenv
30+
run: pip install pipenv
31+
32+
- name: Install dependencies
33+
run: pipenv install --dev
34+
35+
- name: Run tests
36+
run: pipenv run pytest
37+
38+
version:
39+
name: Extract Version
40+
runs-on: ubuntu-latest
41+
needs: test
42+
outputs:
43+
version: ${{ steps.get_version.outputs.version }}
44+
45+
steps:
46+
- name: Checkout repository
47+
uses: actions/checkout@v4
48+
49+
- name: Extract version from __version__.py
50+
id: get_version
51+
run: |
52+
VERSION=$(python3 -c "exec(open('src/sum_impact_assessment/__version__.py').read()); print(__version__)")
53+
echo "version=$VERSION" >> $GITHUB_OUTPUT
54+
echo "Extracted version: $VERSION"
55+
56+
build:
57+
name: Build and Push Docker Image
58+
runs-on: ubuntu-latest
59+
needs: [test, version]
60+
61+
steps:
62+
- name: Checkout repository
63+
uses: actions/checkout@v4
64+
65+
- name: Set up Docker Buildx
66+
uses: docker/setup-buildx-action@v3
67+
68+
- name: Log in to GitLab Container Registry
69+
run: echo "${{ secrets.GITLAB_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.GITLAB_USER }} --password-stdin
70+
71+
- name: Build and push Docker image
72+
uses: docker/build-push-action@v6
73+
with:
74+
context: .
75+
file: ./Dockerfile
76+
push: true
77+
tags: |
78+
${{ env.REGISTRY }}/${{ env.PROJECT_PATH }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }}
79+
${{ env.REGISTRY }}/${{ env.PROJECT_PATH }}/${{ env.IMAGE_NAME }}:latest
80+
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.PROJECT_PATH }}/${{ env.IMAGE_NAME }}:latest
81+
cache-to: type=inline
82+
83+
- name: Image Summary
84+
run: |
85+
echo "### Docker Image Built Successfully! :rocket:" >> $GITHUB_STEP_SUMMARY
86+
echo "" >> $GITHUB_STEP_SUMMARY
87+
echo "**Version:** \`${{ needs.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
88+
echo "**Registry:** \`${{ env.REGISTRY }}\`" >> $GITHUB_STEP_SUMMARY
89+
echo "**Image Tags:**" >> $GITHUB_STEP_SUMMARY
90+
echo "- \`${{ env.REGISTRY }}/${{ env.PROJECT_PATH }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
91+
echo "- \`${{ env.REGISTRY }}/${{ env.PROJECT_PATH }}/${{ env.IMAGE_NAME }}:latest\`" >> $GITHUB_STEP_SUMMARY
92+
93+
- name: Logout
94+
run: docker logout ${{ env.REGISTRY }}

Dockerfile

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Multi-stage Dockerfile for SUM Impact Assessment API
2+
# Stage 1: Builder - prepare dependencies
3+
FROM python:3.13-slim AS builder
4+
5+
WORKDIR /app
6+
7+
# Copy requirements file
8+
COPY requirements.txt .
9+
10+
# Install dependencies in a virtual environment
11+
RUN pip install --no-cache-dir --upgrade pip && \
12+
pip install --no-cache-dir -r requirements.txt
13+
14+
# Stage 2: Runtime - minimal production image
15+
FROM python:3.13-slim
16+
17+
WORKDIR /app
18+
19+
# Install runtime dependencies (curl for health checks)
20+
RUN apt-get update && \
21+
apt-get install -y --no-install-recommends curl && \
22+
rm -rf /var/lib/apt/lists/*
23+
24+
# Copy installed packages from builder
25+
COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages
26+
COPY --from=builder /usr/local/bin /usr/local/bin
27+
28+
# Create non-root user
29+
RUN useradd -m -u 1000 appuser && \
30+
chown -R appuser:appuser /app
31+
32+
# Copy application code
33+
COPY --chown=appuser:appuser src/ ./src/
34+
COPY --chown=appuser:appuser run_api.py .
35+
36+
# Switch to non-root user
37+
USER appuser
38+
39+
# Expose API port
40+
EXPOSE 8000
41+
42+
# Health check using the /health endpoint
43+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
44+
CMD curl -f http://localhost:8000/health || exit 1
45+
46+
# Run the FastAPI application with uvicorn
47+
CMD ["python", "-m", "uvicorn", "src.sum_impact_assessment.api.main:app", "--host", "0.0.0.0", "--port", "8000"]

Pipfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# IMPORTANT: After installing new packages with 'pipenv install <package>',
2+
# regenerate requirements.txt for Docker builds by running:
3+
# pipenv requirements > requirements.txt
4+
15
[[source]]
26
url = "https://pypi.org/simple"
37
verify_ssl = true

README.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,134 @@ curl http://localhost:8000/health
175175
curl http://localhost:8000/kpis
176176
```
177177

178+
## Build and Push Docker Image
179+
180+
The project includes CI/CD automation to build and publish Docker images to GitLab Container Registry.
181+
182+
### Version Management
183+
184+
The API version is managed through semantic versioning in `src/sum_impact_assessment/__version__.py`:
185+
186+
```python
187+
__version__ = "0.0.0" # MAJOR.MINOR.PATCH
188+
```
189+
190+
**To release a new version:**
191+
192+
1. Update the version number in `src/sum_impact_assessment/__version__.py`
193+
2. Commit the version change
194+
3. Trigger the Docker build workflow (see below)
195+
196+
Follow [semantic versioning](https://semver.org/) guidelines:
197+
- **MAJOR**: Breaking changes
198+
- **MINOR**: New features, backward compatible
199+
- **PATCH**: Bug fixes, backward compatible
200+
201+
### Requirements Management
202+
203+
The project uses `pipenv` for dependency management but generates `requirements.txt` for Docker builds.
204+
205+
**After installing new packages:**
206+
207+
```bash
208+
# Install a new package
209+
pipenv install <package-name>
210+
211+
# Regenerate requirements.txt for Docker
212+
pipenv requirements > requirements.txt
213+
214+
# Commit both Pipfile.lock and requirements.txt
215+
git add Pipfile.lock requirements.txt
216+
git commit -m "Add <package-name> dependency"
217+
```
218+
219+
> **Important:** Always regenerate `requirements.txt` after modifying dependencies to keep Docker builds in sync.
220+
221+
### Manual Docker Build (Local)
222+
223+
Build and test the Docker image locally before pushing to registry:
224+
225+
```bash
226+
# Build the image
227+
docker build -t sum-impact-api:local .
228+
229+
# Run the container
230+
docker run -p 8000:8000 \
231+
-e DB_HOST=host.docker.internal \
232+
-e DB_PORT=3306 \
233+
-e DB_USER=your_user \
234+
-e DB_PASSWORD=your_password \
235+
-e DB_NAME=sum_odp \
236+
sum-impact-api:local
237+
238+
# Test the health endpoint
239+
curl http://localhost:8000/health
240+
```
241+
242+
### CI/CD Workflow (GitHub Actions)
243+
244+
The project uses GitHub Actions to automatically build and push Docker images to GitLab Container Registry.
245+
246+
**Workflow file:** `.github/workflows/docker-build.yml`
247+
248+
**Prerequisites:**
249+
250+
Configure the following secrets and variables in your GitHub repository:
251+
252+
**Secrets (Settings → Secrets and variables → Actions → Secrets):**
253+
- `GITLAB_REGISTRY` - GitLab Container Registry URL (e.g., `registry.gitlab.com`)
254+
- `GITLAB_USER` - GitLab username or deploy token username
255+
- `GITLAB_PASSWORD` - GitLab access token or deploy token password
256+
257+
**Variables (Settings → Secrets and variables → Actions → Variables):**
258+
- `GITLAB_PROJECT_PATH` - GitLab project path (e.g., `inria/sum-odp`)
259+
260+
**Trigger the workflow:**
261+
262+
1. Go to **Actions** tab in GitHub repository
263+
2. Select **"Build and Push Docker image to GitLab Registry"**
264+
3. Click **"Run workflow"**
265+
4. Select branch and click **"Run workflow"**
266+
267+
**Workflow steps:**
268+
269+
1. **Test** - Runs full test suite with pytest
270+
2. **Version** - Extracts version from `__version__.py`
271+
3. **Build** - Builds multi-stage Docker image and pushes with two tags:
272+
- `<registry>/<project>/<image>:<version>` (e.g., `registry.gitlab.com/inria/sum-odp/inocs-sum-odp-api:0.0.0`)
273+
- `<registry>/<project>/<image>:latest`
274+
275+
**Pull the image from GitLab:**
276+
277+
```bash
278+
# Login to GitLab Container Registry
279+
docker login registry.gitlab.com
280+
281+
# Pull specific version
282+
docker pull registry.gitlab.com/inria/sum-odp/inocs-sum-odp-api:0.0.0
283+
284+
# Pull latest
285+
docker pull registry.gitlab.com/inria/sum-odp/inocs-sum-odp-api:latest
286+
```
287+
288+
### Docker Image Details
289+
290+
**Base image:** `python:3.13-slim`
291+
292+
**Features:**
293+
- Multi-stage build for optimized image size
294+
- Non-root user (`appuser`) for security
295+
- Health check on `/health` endpoint
296+
- Exposed port: `8000`
297+
- Automatic dependency installation from `requirements.txt`
298+
299+
**Environment variables:**
300+
301+
All configuration is done via environment variables. See `.env.example` for available options.
302+
303+
**Image tags:**
304+
305+
- `<version>` - Immutable version tag (e.g., `0.0.0`, `1.2.3`)
306+
- `latest` - Always points to the most recent build
307+
308+

0 commit comments

Comments
 (0)