noc2rnalnoc2rnal
June 2025
Python DockerRedis

Introduction

Having minimal interaction with Flask I can without trouble say that it is ideal for microservices because its minimal, unopinionated core that lets you spin up tiny HTTP APIs in minutes. Combine that with Docker and you get rock-solid isolation, repeatable builds, and easy scaling across clusters. Python’s rich ecosystem (pandas, NumPy, PyTorch) makes complex data manipulation a breeze, so I can preprocess, analyze, or transform datasets swiftly. The power of this setup can be also found not only in creating processing microservices but also for lightweight models with the use of Hugging Face, serve inference via Flask, and ship it in a single Docker image—making deployment of custom language models effortless.

To som-up why Python + Flask?

  • Python excels at data processing, ML, scientific libraries.
  • Flask is a micro-framework: it gives you just enough HTTP plumbing without imposing structure.
  • Rapid development: install Flask, write a few lines of code, and you’re serving JSON.

A microservice can be defined in this example as:

  1. Small – single responsibility (e.g., “calculate heat index”).
  2. Independent – its own codebase, tests, and deploy pipeline.
  3. Loosely coupled – talks to other services over lightweight protocols (usually HTTP/JSON).

Unlike a monolith, you can scale, deploy, or rewrite each microservice separately.

Prerequisites

  • Python 3.9+
  • Docker & Docker Compose
  • Flask, REST, and Docker

Project Structure:

service/
├── run.py
├── venv/
├── app/
│   ├── __init__.py
│   ├── routes.py
│   ├── controllers/
│   │   └── app_controller.py
│   ├── services/
│   │   ├── __init__.py
│   │   ├── service.py
│   └── utils/
│       └── logger.py
├── tests/
│   ├── __init__.py
│   └── test_app_controller.py
├── .env
├── .env.production
├── gunicorn_config.py
├── requirements.txt
├── Dockerfile
├── .gitignore
├── .dockerignore
├── docker-compose.yml
├── app.log
└── README.md

OVERVIEW :

  • run.py: entrypoint—it’s the script invoked (or container runs) to actually launch the Flask server
  • venv/: isolated Python environment containing all installed packages.
  • app/init.py: defines create_app()
  • Dockerfile & docker-compose.yml: containerization configs.
  • app/routes.py: declares HTTP routes and used for delegating to controller
  • app/controllers: fetching, computing, and formatting data for responses
  • app/utils/logger.py: logging (handlers, formatters) so all modules log uniformly to app.log
  • app.log: Runtime log file where all modules write their messages.
  • requirements.txt: lists the minimal Python dependency for reproducible installs.
  • .dockerignore: excludes things like venv/, .env*, __pycache__/, *.pyc, app.log, tests/ so your image stays small
  • docker-compose.yml: wiring up the .env, bind ports, mount volumes, and link to things like Redis/Celery sidecars or a database

Environment Setup

If you already have a project that you want to turn into a an API start with generating requirements.txt:

pip freeze > requirements.txt

Example of app/requirements.txt which partially can be added into any reqs:

flask==2.2.5
transformers==4.35.0
tensorflow==2.13.0

Install dependencies:

pip install -r requirements.txt

Flask API Implementation (What You Are Here For)

Minimal Set Up

In app/run.py:

from app import create_app

app = create_app()

if __name__ == "__main__":
    # Launches Flask’s built-in server on 0.0.0.0:5003
    app.run(host="0.0.0.0", port=5003)

Entry Point: imports your app factory, then calls app.run(...) to start the HTTP server.

In app/__init__.py:

from flask import Flask
from .routes import main_bp

def create_app():
    app = Flask(__name__)
    # register all routes under /api
    app.register_blueprint(main_bp, url_prefix="/api")
    return app

Instantiate Flask: Holds create_app(), which instantiates Flask and wires up your routes under /api.

In app/routes.py:

from flask import Blueprint, request, jsonify
from app.controllers.service_controller import process_service

main_bp = Blueprint("main", __name__)

@main_bp.route("/service", methods=["POST"])
def service():
    payload = request.get_json() or {}
    slug = payload.get("slug")
    data = payload.get("data")

    if not slug or data is None:
        return jsonify(success=False, error="Both 'slug' and 'data' required"), 400

    result = process_service(slug, data)
    return jsonify(success=True, result=result)

POST: Defines a single POST endpoint at /api/service that parses JSON, validates slug+data, and delegates to the controller

Sample of app/controllers/service_controller.py:

def process_service(slug, data):
    """
    replace with your own computation, DB calls, etc
    """
    return {
        "slug": slug,
        "data_count": len(data)
    }

controller&service: the bulk of computation should be done here

Docker + Flask (The Killer Starter Combo)

In Dockerfile:

# 1. lightweight Python image
FROM python:3.11-slim

# 2. working dir
WORKDIR /app

# 3. copy and install deps first
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 4. copy code
COPY . .

# 5. switch to a non-root user
RUN useradd --create-home appuser && chown -R appuser /app
USER appuser

# 6. expose the port on app listens
EXPOSE 5003

  • ⚗️ Isolation & Consistency

    Docker containers bundle your Flask app, its exact Python runtime, dependencies, config files and even ML model weights into one immutable image. That means “it works on my machine” translates to every server, CI pipeline, or Kubernetes cluster.

  • 🧩 Loose Coupling & Scalability

    Guess what, this does not only apply to the service. Imagine you wanted to launch a very light weight LLM? By shipping each piece, analysis, LLM inference, heavy data crunching—as its own Dockerized microservice, you decouple release cycles, team ownership, and resource scaling. Need more CPU for batch jobs? Spin up extra containers just for your compute-heavy service.

  • 🦮 Best-of-Breed

    Python’s ecosystem is vast and to show you examples you are well aware ofNumPy/Pandas for data, Hugging Face/Transformers for LLMs, SciPy for ML and so on can live naturally inside Flask. You can route /predict or /analyze calls into pure-Python functions that load models or run algorithms, then return JSON. Meanwhile your main app—React, Node, Go, whatever—just issues HTTP calls.

  • 🏍️ Lightweight & Rapid Iteration

    This brings us to the next point of Flask and Docker setup. Flask’s minimal core means you add only what you need: a WSGI server, maybe CORS, rate-limiting, Talisman for security headers. With Docker you rebuild images in seconds, run integration tests in containers, and roll out updates with zero-downtime deploys (via rolling updates in Kubernetes or Docker Swarm)

🦄 Go From Project to Production (WSGI)

In our example Flask is the chosen API framework for this service, so what is WSGI and why do we need it? A WSGI is a server that actually listens on sockets and manages worker processes. A great complementary WSGI in project like this is Gunicorn.

pip install gunicorn

In our entry-point run.py we expose Flask instance as app to launch:

gunicorn \
  --bind 0.0.0.0:5003 \
  --workers 4 \
  --threads 2 \
  run:app
  • --bind: tells Gunicorn which host/port to listen on.
  • --workers: is the number of separate Python processes (good for CPU‐bound work).
  • --threads: gives each worker a thread pool (helps with I/O concurrency).

We can also create config file gunicorn_config.py:

bind       = "0.0.0.0:5003"
workers    = 4
threads    = 2
timeout    = 120      # sec before worker killed
accesslog  = "-"      # stdout
errorlog   = "-"      # stderr

update Dockerfile:

# 7. run Gunicorn with 4 workers and 2 threads each
CMD ["gunicorn", "run:app", \
     "-w", "4", \
     "--threads", "2", \
     "-b", "0.0.0.0:5003"]

Gunicorn transforms our Flask app (exported as run:app) into a true multi-process HTTP server, spawning configurable worker processes and threads (via -w/--threads flags or gunicorn_config.py) that bind to 0.0.0.0:5003 inside the Docker container. In our Dockerfile CMD, Gunicorn boots with production settings—isolating Python interpreters per worker, handling graceful restarts, and maximizing CPU utilization—so the service can handle concurrent /api/service requests at scale.

TL;DR:

  • Flask for lightweight Python APIs
  • Docker for reproducible, isolated deployments
  • Gunicorn for high-performance, multi-worker request handling

Conclusion

Even without diving into the greasy details of this setup you can already note that the setup is effortless. It shows great power of python framework and speed at which you can develop a production-grade microservice platform. With these technologies and proper maintenance you get fast development cycles, consistent deployments, and the ability to right-size resources per service—whether you’re crunching numbers or even interested in serving up language-model predictions.