Service: Python Runner - EyevinnOSC/community GitHub Wiki

Getting Started

Python Runner lets you deploy a Python web application directly from your GitHub repository (or an S3 zip archive) and run it as a managed service in Open Source Cloud. OSC clones your repository, installs dependencies from requirements.txt, and starts your application automatically.

Prerequisites

  • An OSC account. Sign up here.
  • A Python web application in a GitHub repository (public or private) or packaged as a zip file in an S3 bucket.
  • Your application must listen on port 8080 (the default port used by the Python runner).

Create a Python Runner instance

Navigate to Python Runner in the OSC web console, click Create python-runner, and fill in the fields:

Field Required Description
name Yes Unique name for this instance. Alphanumeric and underscores only.
SourceUrl Yes GitHub repository URL (e.g. https://github.com/org/repo) or S3 URL to a zip archive (e.g. s3://bucket/app.zip). Append #branch to target a specific branch.
GitHubToken No Personal access token for private GitHub repositories. Store as a service secret and reference it as {{secrets.mytoken}}.
AwsAccessKeyId No AWS access key for S3 sources.
AwsSecretAccessKey No AWS secret key for S3 sources. Use a service secret reference.
AwsRegion No AWS region for S3 access.
S3EndpointUrl No S3-compatible endpoint URL (e.g. a MinIO instance URL).
OscAccessToken No OSC personal access token, required if your application calls other OSC services.
ConfigService No Name of an Application Config Service instance used to inject environment variables at startup.

Click Create. OSC clones the repository, installs dependencies, and starts your application. Once the status shows Running, click the generated URL to reach your app.

How auto-detection works

The Python runner examines your dependencies and repository root to decide how to start your application. The following table shows all four paths, evaluated in this order:

Detected condition What the runner does
flask AND gunicorn in requirements.txt Runs python -m gunicorn <module>:app --bind 0.0.0.0:${PORT}
gunicorn in requirements.txt AND gunicorn.conf.py or gunicorn_config.py present at repo root Runs python -m gunicorn -c <config_file> <module>:app
fastapi or starlette in requirements.txt (with uvicorn) Runs python -m uvicorn <module>:app --host 0.0.0.0 --port ${PORT}
None of the above Runs python app.py or python main.py (plain Python)

A few things to note:

  • gunicorn installed alone, without flask and without a gunicorn.conf.py, does NOT start Gunicorn. The runner falls through to plain Python.
  • FastAPI and Starlette apps use uvicorn directly, even if gunicorn is also listed in requirements.txt.
  • The runner passes only --bind (Flask path) or -c (config-file path). No workers, timeout, or log-level settings are injected by the platform.

Procfile is NOT supported. The Python runner does not parse or honor a Procfile. If your repository contains a Procfile, it is silently ignored. Use the auto-detection patterns above instead.

Port: the runner uses port 8080 by default (ENV PORT=8080 in the runner Dockerfile). Your application must listen on $PORT (or hardcode 8080).

Custom Dockerfile: a custom Dockerfile.osc is supported only when using eyevinn-python-runner as a directly-provisioned catalog service instance. If you are deploying a My App (via create-my-app or the OSC web console My Apps section), My App runners do NOT read Dockerfile.osc from your repository — use setup.sh instead (see System Dependencies below). For direct service instances, Dockerfile.osc is only needed when auto-detection cannot handle your startup requirements (for example, a non-standard entry point name or a multi-stage build). For standard Flask and FastAPI apps, auto-detection works without any extra files.

Configuring Gunicorn

By default, Gunicorn runs with its built-in defaults (one worker process, 30-second timeout). To adjust workers, timeouts, logging, or any other setting, place a gunicorn.conf.py file at the repository root.

Using gunicorn.conf.py

When the runner finds gunicorn.conf.py or gunicorn_config.py at the repo root, it passes it to Gunicorn via -c. This works for both the Flask+Gunicorn path and the standalone config-file path.

Example gunicorn.conf.py:

import os

bind = f"0.0.0.0:{os.environ.get('PORT', '8080')}"
workers = 2
timeout = 120
accesslog = "-"
errorlog = "-"
loglevel = "info"

Important: always read the port from the environment. The platform sets the PORT variable at runtime. If you hardcode a port number in gunicorn.conf.py, it will conflict with the --bind argument the runner passes for Flask apps, or produce the wrong port for other configurations. Use os.environ.get('PORT', '8080') as shown above.

The workers = 2 value is a starting point. A common rule of thumb is (2 * CPU cores) + 1, but on OSC each instance runs in a container with limited CPU, so start with 2 and adjust based on observed load.

accesslog = "-" and errorlog = "-" send logs to stdout and stderr, making them visible in the OSC log viewer. Without these, Gunicorn writes to files by default and the logs are not visible in the platform UI.

For the full list of supported settings, see the official Gunicorn configuration reference.

FastAPI and Starlette apps

Gunicorn configuration does not apply to FastAPI or Starlette applications. These frameworks are started with uvicorn directly, and any gunicorn.conf.py in the repository root is ignored. To configure uvicorn settings for these frameworks when using a direct service instance, use a custom Dockerfile.osc. For My App deployments, use setup.sh instead (see System Dependencies below).

Advanced: GUNICORN_CMD_ARGS environment variable

Gunicorn reads the GUNICORN_CMD_ARGS environment variable and treats its value as additional command-line arguments. This lets you pass settings without a config file, using the platform parameter store to inject them at runtime.

To use this approach:

  1. Create an Application Config Service instance (the parameter store).
  2. Add a key named GUNICORN_CMD_ARGS with the value of your arguments, for example: --workers=4 --timeout=60.
  3. Set ConfigService to the name of your App Config Service instance when creating the Python Runner.

The platform loads the parameter store values as environment variables before starting your application, so Gunicorn picks up GUNICORN_CMD_ARGS automatically.

Note that GUNICORN_CMD_ARGS does not override values set in gunicorn.conf.py. If you use both, settings in GUNICORN_CMD_ARGS take precedence over config file values for the same option.

Minimal Flask example

This example requires no Procfile, no Dockerfile, and no extra configuration:

myapp/
├── app.py
└── requirements.txt
# app.py
import os
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello from Python Runner!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))
# requirements.txt
flask
gunicorn

The runner detects flask and gunicorn in requirements.txt, then starts the app with python -m gunicorn app:app --bind 0.0.0.0:8080.

Minimal FastAPI example

myapi/
├── main.py
└── requirements.txt
# main.py
import os
from fastapi import FastAPI

app = FastAPI()

@app.get('/')
def root():
    return {'message': 'Hello from Python Runner!'}
# requirements.txt
fastapi
uvicorn[standard]

The runner detects uvicorn in requirements.txt and main.py, then starts the app with python -m uvicorn main:app --host 0.0.0.0 --port 8080.

Application requirements

To summarise, OSC expects your Python application to:

  1. Include a requirements.txt at the repository root listing all dependencies.
  2. Listen on port 8080 (or read from the PORT environment variable).
  3. Use a supported entry point name (app.py, wsgi.py, or main.py) so auto-detection can locate your app object.

Do not add a Procfile — it will be silently ignored by the runner.

Using a private GitHub repository

  1. Create a GitHub personal access token with repo scope.
  2. Go to Python RunnerService SecretsNew Secret. Name it (e.g. githubtoken) and paste the token.
  3. When creating the instance, set SourceUrl to your private repository URL and GitHubToken to {{secrets.githubtoken}}.

Using Application Config Service

To inject runtime configuration as environment variables, create an Application Config Service instance first, add your key-value pairs, and set the ConfigService field to the name of that instance when creating the Python Runner. OSC loads the config values as environment variables before starting your application.

Source code from S3

Package your project as a zip file:

cd myproject && zip -r ../myproject.zip ./

Upload to your S3 bucket, then set SourceUrl to s3://mybucket/myproject.zip and provide AwsAccessKeyId, AwsSecretAccessKey, and AwsRegion (or S3EndpointUrl for MinIO).

CLI usage

osc create eyevinn-python-runner myapp \
  -o SourceUrl="https://github.com/myorg/myapp"

With a private repository:

osc create eyevinn-python-runner myapp \
  -o SourceUrl="https://github.com/myorg/myapp" \
  -o GitHubToken="{{secrets.githubtoken}}"

System Dependencies (setup.sh)

If your Python My App (deployed via the OSC My Apps feature) needs OS-level packages such as ffmpeg, libsrt, or gcc, place a setup.sh file at the repository root. The python-runner executes this script as root at container start, after pip install completes and before the application process starts.

Note: setup.sh applies to My App python deployments. When using eyevinn-python-runner as a direct catalog service instance, system dependencies can also be installed via Dockerfile.osc.

Execution order in the python-runner container:

  1. Config-service environment variables are loaded (parameter store) — pip-time env vars work here
  2. pip install (reads requirements.txt, pyproject.toml, or setup.py)
  3. setup.sh runs (as root, after pip install)
  4. Application process starts

Key constraints:

  • setup.sh runs after pip install. It cannot install C headers or shared libraries that a compiled wheel needs at build/compile time. If a package requires libsrt-dev to compile a wheel, the compile step has already failed by the time setup.sh runs. Use setup.sh for runtime binaries, not build-time dependencies.
  • Config-service environment variables are available during pip install (step 1), so you can use env-var-controlled index URLs or auth tokens for private packages.
  • setup.sh runs as root — no sudo is required.

Example setup.sh to install ffmpeg:

#!/bin/bash
set -e
apt-get update -qq
apt-get install -y --no-install-recommends ffmpeg
apt-get clean && rm -rf /var/lib/apt/lists/*

Place this file at the root of your repository alongside requirements.txt.

Resources

  • Python Runner on OSC
  • GitHub repository
  • Application Config Service — inject environment variables at startup
  • Web Runner — same concept for Node.js applications
  • Golang Runner — same concept for Go applications
⚠️ **GitHub.com Fallback** ⚠️