Containerized ML inference service with automated CI/CD
2.8K
Docker-based ML model runner using Hugging Face's distilgpt2 for text generation, with automated GitHub Actions pipeline for building, testing, and deploying secure Docker images.
A model runner is a containerized service that loads a machine learning model and exposes it via an API. This project demonstrates:

The complete CI/CD pipeline from code push to deployment
docker-model-runner/
āāā README.md # This file
āāā app.py # Flask API with model runner
āāā requirements.txt # Python dependencies
āāā Dockerfile # Multi-stage Docker build
āāā Makefile # Development commands
āāā .gitignore # Git exclusions
āāā .dockerignore # Docker build exclusions
āāā .github/
āāā workflows/
āāā docker-build.yml # CI/CD pipeline
git clone https://github.com/theCaptN21/docker-model-runner.git
cd docker-model-runner
Replace YOUR_USERNAME with your GitHub username.
Why SonarCloud? It provides automated code quality and security analysis on every push.
Sign up at https://sonarcloud.ioā
Import Your Repository
docker-model-runnerGet Your Token
github-actionsUser TokenAdd Secrets to GitHub
Secret 1: SONAR_TOKEN
SONAR_TOKENSecret 2: SONAR_PROJECT_KEY
SONAR_PROJECT_KEYtheCaptN21_docker-model-runner)Secret 3: SONAR_ORGANIZATION
SONAR_ORGANIZATIONthecaptn21)Create Docker Hub Access Token
github-actionsAdd Secrets to GitHub
DOCKERHUB_USERNAME: Your Docker Hub usernameDOCKERHUB_TOKEN: Your access token from aboveBefore pushing to GitHub, verify everything works:
# Build the Docker image
make build
# Run the container
make run
# In another terminal, test the API
curl http://localhost:5000/health
curl -X POST http://localhost:5000/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "The future of AI is", "max_length": 50}'
# Stop the container
make clean
git add .
git commit -m "Initial commit: Docker Model Runner"
git push origin main
# Start the container
docker run -d -p 5000:5000 --name model-runner YOUR_DOCKERHUB_USERNAME/docker-model-runner:latest
# Wait for model to load (check logs)
docker logs -f model-runner
# Test health endpoint
curl http://localhost:5000/health
# Expected: {"status":"healthy"}
# Test text generation
curl -X POST http://localhost:5000/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "Once upon a time", "max_length": 100}'
# Stop container
docker stop model-runner && docker rm model-runner
Test 1: Health Check (Server Running)
curl http://localhost:5000/health
Output: {"status":"healthy"} - proves Flask API is running
Test 2: Generate Text (Model Loaded)
curl -X POST http://localhost:5000/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello world", "max_length": 30}'
Output shows generated text - proves distilgpt2 model is loaded and generating
Test 3: Different Prompts (Model Intelligence)
# Creative writing
curl -X POST http://localhost:5000/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "In a galaxy far away", "max_length": 50}'
# Technical content
curl -X POST http://localhost:5000/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "Machine learning is", "max_length": 50}'
Different outputs for different prompts - proves model is doing inference
Step 1: Modify the model prompt processing
Edit app.py, find the generate function and add this line after getting the prompt:
# Add this line to make prompts more creative
prompt = f"[CREATIVE] {prompt}"
Step 2: Test locally
docker build -t model-runner:test .
docker run -d -p 5000:5000 --name test model-runner:test
curl -X POST http://localhost:5000/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello", "max_length": 30}'
# Notice the prompt now has [CREATIVE] prefix in the response
Step 3: Push the change
git add app.py
git commit -m "Add creative prefix to prompts"
git push origin main
Step 4: Watch automated deployment
docker pull YOUR_DOCKERHUB_USERNAME/docker-model-runner:latest
docker run -d -p 5000:5000 YOUR_DOCKERHUB_USERNAME/docker-model-runner:latest
# Test and see the [CREATIVE] prefix in responses
latest, main-<sha>, branch name# Stage 1: Builder (dependencies compilation)
FROM python:3.11-slim AS builder
# Install and compile dependencies
# Stage 2: Runtime (minimal production image)
FROM python:3.11-slim
# Copy only what's needed, run as non-root
Benefits:
Non-root User
appuser (not root)Minimal Base Image
python:3.11-slim (only essentials)Health Checks
Dependency Pinning
make help # Show available commands
make build # Build Docker image locally
make test # Build and run full test suite
make run # Run container locally
make clean # Stop and remove containers/images
GET /health
curl http://localhost:5000/health
Response: {"status": "healthy"}
POST /generate
curl -X POST http://localhost:5000/generate \
-H "Content-Type: application/json" \
-d '{
"prompt": "Your text here",
"max_length": 50
}'
Response:
{
"prompt": "Your text here",
"generated_text": "Your text here and the model continues..."
}
docker-model-runnerSonarCloud Error:
SONAR_TOKEN secret is addeddocker-model-runner in SonarCloudDocker Push Error:
DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets# Check logs
docker logs model-runner
# Common issues:
# - Port 5000 already in use: use -p 5001:5000
# - Model download failed: check internet connection
# - Memory issues: Docker Desktop needs 4GB+ RAM
First run downloads the model (~250MB):
docker logs -f model-runnerCurrent dependencies (all vulnerabilities fixed):
flask==3.1.0 # Latest stable
transformers==4.48.0 # All CVEs fixed
torch==2.5.1 # Stable version
numpy==2.1.0 # Latest stable
Content type
Image
Digest
sha256:19587f1d1ā¦
Size
3 GB
Last updated
8 months ago
docker pull 021bl00m/docker-model-runner