# Running Your Backend Server with the SeaLights Python Agent

To capture code coverage from a running backend server, the server's Python process must load the SeaLights agent at startup. This page describes four methods for doing that, ordered from most-recommended to most-specialized. Pick the first method that fits your environment.

Every snippet below is gated on a single environment variable, **`ENABLE_SEALIGHTS=true`**. When the variable is unset or any other value, the snippet is a no-op, and the application behaves as if SeaLights were never integrated. This lets you keep the same artifacts, images, and pipelines for instrumented and non-instrumented runs.

### TL;DR — which method should I use?

| Method                                    | Situation                                                                             | Server                                               |
| ----------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| **Method 1: Python startup hook**         | Plain Python entrypoint, you control the runtime env                                  | `python app.py`, custom runner, Uvicorn (standalone) |
| **Method 2: `sl-python` wrapper**         | You cannot modify the image or source, but you can change how the process is launched | Any                                                  |
| **Method 3: Server-specific integration** | You're running a forking WSGI/ASGI server and own the repo                            | uWSGI, Gunicorn, Uvicorn (with workers)              |
| **Method 4: CI-managed injection**        | You want zero SeaLights code in the repo — patches happen in the pipeline only        | uWSGI, Gunicorn, Uvicorn, or fallback to Method 1    |

Method 1 is the default. Method 2 is the cleanest when you don't own the image. Method 3 is the right call when SeaLights is a normal part of the codebase. Method 4 is for organizations that want the source repo to stay free of any test-tooling references — the pipeline injects, runs, and reverts.

***

### Prerequisites

1. Install the agent in the same Python environment as your application: `pip install sealights-python-agent`
2. Provide credentials and a build session ID. The agent reads them from files in the working directory by default (`sltoken.txt` and `buildSessionId.txt`). If those files live elsewhere, point to them with `SL_TOKEN_FILE` and `SL_BUILD_SESSION_ID_FILE`.
3. Set the lab ID (`SL_LABID`) for the environment you're capturing from.
4. Set `ENABLE_SEALIGHTS=true` wherever you want the agent active.

***

### Method 1 — Python startup hook (recommended)

A `sitecustomize.py` file is a standard Python startup hook that the interpreter imports automatically during initialization (unless started with `-S`). Placing a `sitecustomize.py` on `PYTHONPATH` preloads the SeaLights agent *before any application code runs*, with no changes to your application source.

The file itself is one line:

```python
import python_agent.init
```

The conditional logic lives in your **launch environment**, not in the file or the app. If `ENABLE_SEALIGHTS=true`, the hook directory is added to `PYTHONPATH`; otherwise it isn't.

{% hint style="info" %}
This method does not work with uWSGI or Gunicorn out of the box, because those servers fork workers after the master interpreter has already started. Use Method 3 or Method 4 for those servers.
{% endhint %}

{% tabs %}
{% tab title="Bash" %}

```bash
#!/usr/bin/env bash
set -euo pipefail

if [[ "${ENABLE_SEALIGHTS:-false}" == "true" ]]; then
  mkdir -p /opt/python-startup
  echo "import python_agent.init" > /opt/python-startup/sitecustomize.py
  export PYTHONPATH="/opt/python-startup:${PYTHONPATH:-}"
  pip install sealights-python-agent
fi

python server.py
```

{% endtab %}

{% tab title="GitHub Actions" %}

```yaml
- name: Configure SeaLights startup hook
  if: env.ENABLE_SEALIGHTS == 'true'
  run: |
    mkdir -p $GITHUB_WORKSPACE/.sealights
    echo "import python_agent.init" > $GITHUB_WORKSPACE/.sealights/sitecustomize.py
    pip install sealights-python-agent
    echo "PYTHONPATH=$GITHUB_WORKSPACE/.sealights:$PYTHONPATH" >> $GITHUB_ENV

- name: Run application
  run: python server.py
```

{% endtab %}

{% tab title="PowerShell" %}

```powershell
if ($env:ENABLE_SEALIGHTS -eq 'true') {
  $hookDir = "$env:USERPROFILE\sealights-startup"
  New-Item -ItemType Directory -Force -Path $hookDir | Out-Null
  Set-Content -Path "$hookDir\sitecustomize.py" -Value "import python_agent.init"
  $env:PYTHONPATH = "$hookDir;$env:PYTHONPATH"
  pip install sealights-python-agent
}

python server.py
```

{% endtab %}

{% tab title="Dockerfile" %}

```dockerfile
FROM python:3.9-slim

ARG ENABLE_SEALIGHTS=false
ENV ENABLE_SEALIGHTS=${ENABLE_SEALIGHTS}

# ... your application setup ...

RUN if [ "$ENABLE_SEALIGHTS" = "true" ]; then \
      pip install sealights-python-agent && \
      mkdir -p /opt/python-startup && \
      echo "import python_agent.init" > /opt/python-startup/sitecustomize.py ; \
    fi

# PYTHONPATH only points at the hook dir when SeaLights is enabled.
# An empty PYTHONPATH is harmless to Python.
ENV PYTHONPATH=${ENABLE_SEALIGHTS:+/opt/python-startup}

ENTRYPOINT ["python", "server.py"]
```

Build with: `docker build --build-arg ENABLE_SEALIGHTS=true -t myapp:sealights .`
{% endtab %}

{% tab title="Kubernetes" %}
The image is built once with the hook directory baked in (see Dockerfile above with `ENABLE_SEALIGHTS=true`). Toggle SeaLights per-deployment by setting or unsetting `PYTHONPATH`:

```yaml
env:
  - name: ENABLE_SEALIGHTS
    value: "true"
  - name: PYTHONPATH
    value: /opt/python-startup    # remove this line to disable
  - name: SL_LABID
    value: Lab1
  - name: SL_TOKEN_FILE
    value: /etc/sealights/sltoken.txt
```

{% endtab %}
{% endtabs %}

### Method 2 — `sl-python` wrapper

The agent ships with an `sl-python` CLI that wraps any Python invocation. Use this when you cannot modify the application source or `PYTHONPATH`, but you can change the launch command.

```sh
sl-python run --cov-report /path/to/report.xml --labid Lab1 python <your server and args...>
```

The first argument after `run` must be an executable.

{% tabs %}
{% tab title="Bash" %}

```bash
#!/usr/bin/env bash
set -euo pipefail

if [[ "${ENABLE_SEALIGHTS:-false}" == "true" ]]; then
  pip install sealights-python-agent
  exec sl-python run --labid "${SL_LABID}" -- python server.py
else
  exec python server.py
fi
```

{% endtab %}

{% tab title="GitHub Actions" %}

```yaml
- name: Install SeaLights
  if: env.ENABLE_SEALIGHTS == 'true'
  run: pip install sealights-python-agent

- name: Run application
  run: |
    if [[ "$ENABLE_SEALIGHTS" == "true" ]]; then
      sl-python run --labid "$SL_LABID" -- python server.py
    else
      python server.py
    fi
```

{% endtab %}

{% tab title="PowerShell" %}

```powershell
if ($env:ENABLE_SEALIGHTS -eq 'true') {
  pip install sealights-python-agent
  sl-python run --labid $env:SL_LABID -- python server.py
} else {
  python server.py
}
```

{% endtab %}

{% tab title="DockerFile" %}
The wrapper is conditionally installed and the entrypoint chooses at runtime:

```dockerfile
FROM python:3.9-slim

ARG ENABLE_SEALIGHTS=false
ENV ENABLE_SEALIGHTS=${ENABLE_SEALIGHTS}

# ... your application setup ...

RUN if [ "$ENABLE_SEALIGHTS" = "true" ]; then \
      pip install sealights-python-agent ; \
    fi

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
```

```bash
# entrypoint.sh
#!/usr/bin/env sh
if [ "${ENABLE_SEALIGHTS:-false}" = "true" ]; then
  exec sl-python run --labid "${SL_LABID}" -- python server.py
else
  exec python server.py
fi
```

{% endtab %}

{% tab title="Kubernetes" %}
This approach relies on an **init-container pattern (no image rebuild)**.

When you don't own the application image, an init-container installs `sl-python` into a shared volume, and the application container's command is rewritten to use it. The init-container only runs when `ENABLE_SEALIGHTS=true`.

```yaml
spec:
  volumes:
    - name: sealights
      emptyDir: {}
  initContainers:
    - name: install-sl-python
      image: python:3.9-slim
      env:
        - name: ENABLE_SEALIGHTS
          value: "true"        # set to "false" to disable injection
      command:
        - sh
        - -c
        - |
          if [ "$ENABLE_SEALIGHTS" = "true" ]; then
            pip install sealights-python-agent
            cp "$(which sl-python)" /sealights/
          fi
      volumeMounts:
        - name: sealights
          mountPath: /sealights
  containers:
    - name: app-container
      image: myapp:latest
      env:
        - name: ENABLE_SEALIGHTS
          value: "true"
        - name: SEALIGHTS_AGENT_TOKEN
          valueFrom:
            secretKeyRef:
              name: sealights-token-secret
              key: agent-token
      command:
        - sh
        - -c
        - |
          if [ "$ENABLE_SEALIGHTS" = "true" ]; then
            exec /sealights/sl-python run --token "$SEALIGHTS_AGENT_TOKEN" -- python app.py
          else
            exec python app.py
          fi
      volumeMounts:
        - name: sealights
          mountPath: /sealights
```

{% endtab %}
{% endtabs %}

### Method 3 — Server-specific integration

Forking WSGI/ASGI servers need the agent loaded *inside each worker* after fork, which means a small change inside a config file (`uwsgi.ini`, `gunicorn.conf.py`) or the app entrypoint (Uvicorn). The change itself is gated on `ENABLE_SEALIGHTS`.

If keeping SeaLights references out of the repo is a requirement, skip this method and use **Method 4** instead — same end-state, but the pipeline performs the edit.

#### uWSGI

uWSGI requires four flags. The minimum-intrusion approach is a small overlay INI file that's only included when SeaLights is enabled.

<table><thead><tr><th width="278">Flag</th><th>Purpose</th></tr></thead><tbody><tr><td><code>--enable-threads</code></td><td>Agent uses background threads to report coverage</td></tr><tr><td><code>--single-interpreter</code></td><td>One interpreter per worker (required for instrumentation)</td></tr><tr><td><code>--lazy-apps</code></td><td>App loads in each worker after fork, not in the master</td></tr><tr><td><code>--import python_agent.init</code></td><td>Loads the SeaLights agent at worker start</td></tr></tbody></table>

`sealights.ini` (committed once, never edited):

```ini
[uwsgi]
enable-threads = true
single-interpreter = true
lazy-apps = true
import = python_agent.init
```

{% tabs %}
{% tab title="Bash" %}

```bash
#!/usr/bin/env bash
set -euo pipefail

UWSGI_ARGS=(--ini uwsgi.ini)
if [[ "${ENABLE_SEALIGHTS:-false}" == "true" ]]; then
  pip install sealights-python-agent
  UWSGI_ARGS+=(--ini sealights.ini)   # overlays the base config
fi

exec uwsgi "${UWSGI_ARGS[@]}"
```

{% endtab %}

{% tab title="GitHub Actions" %}

```yaml
- name: Install SeaLights
  if: env.ENABLE_SEALIGHTS == 'true'
  run: pip install sealights-python-agent

- name: Run uWSGI
  run: |
    ARGS="--ini uwsgi.ini"
    [[ "$ENABLE_SEALIGHTS" == "true" ]] && ARGS="$ARGS --ini sealights.ini"
    uwsgi $ARGS
```

{% endtab %}

{% tab title="PowerShell" %}

```powershell
$args = @('--ini', 'uwsgi.ini')
if ($env:ENABLE_SEALIGHTS -eq 'true') {
  pip install sealights-python-agent
  $args += @('--ini', 'sealights.ini')
}
uwsgi @args
```

{% endtab %}

{% tab title="Dockerfile" %}
Follow the same pattern as Method 1 — build with `ARG ENABLE_SEALIGHTS`, install conditionally, and let the entrypoint script pick which INI files to load.
{% endtab %}

{% tab title="Kubernetes" %}
Follow the same pattern as Method 1 — build with `ARG ENABLE_SEALIGHTS`, install conditionally, and let the entrypoint script pick which INI files to load.
{% endtab %}
{% endtabs %}

#### Gunicorn

Gunicorn forks workers from a master process. Load the agent in the `post_fork` hook of your Gunicorn config file. Keep one config file in your repo (`gunicorn.conf.py`) with the SeaLights hook gated on `ENABLE_SEALIGHTS`:

```python
# gunicorn.conf.py
import os

def post_fork(server, worker):
    if os.getenv("ENABLE_SEALIGHTS", "false").lower() == "true":
        import python_agent.init
```

Launch (same command for both modes — the flag inside the config decides):

```bash
#!/usr/bin/env bash
set -euo pipefail

if [[ "${ENABLE_SEALIGHTS:-false}" == "true" ]]; then
  pip install sealights-python-agent
fi

exec gunicorn -c gunicorn.conf.py app:app
```

The same launch command works in GitHub Actions, PowerShell, Dockerfile, and K8s — pass `ENABLE_SEALIGHTS` through and the config does the rest.

#### Uvicorn

Uvicorn has no pre-import hook, so a tiny conditional `import` lives at the top of your entry point:

```python
# app.py
import os
if os.getenv("ENABLE_SEALIGHTS", "false").lower() == "true":
    import python_agent.init

# ... rest of your app ...
```

If editing the source is not acceptable, two alternatives:

1. **Prefer Method 1** (Python startup hook) — it works with standalone Uvicorn and requires no source edits at all.
2. **Use Method 4** to inject and revert the snippet in the pipeline.

Launch command is the standard Uvicorn invocation — the gate is inside the source:

```bash
export ENABLE_SEALIGHTS=true
uvicorn app:APP --host 0.0.0.0 --port 9092 --workers 1
```

### Method 4 — CI-managed injection (no source changes)

Method 4 produces the same runtime configuration as Methods 1 and 3, but the edits happen *in the pipeline* — the repo stays free of any SeaLights references. Useful when the source repo is shared with other teams, when test tooling shouldn't appear in code reviews, or when SeaLights is enabled only on specific branches or release lines.

The toolkit is a set of small, single-purpose Bash scripts. Each is **idempotent** (running twice is a no-op the second time) and **reversible** (each creates a `.bak` and supports `--revert`). Use `inject-sealights.sh` as the dispatcher — it detects what's in the working directory and calls the right sub-injector.

#### What it covers

| Detected file in working dir  | Injector called                    | What it does                                                                                                                                                           |
| ----------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| N/A                           | `inject-sealights.sh` (dispatcher) | <p>Detect server type in the current directory and run the matching injector.</p><p>All injectors are idempotent. Pass --revert to roll back changes (.bak files).</p> |
| `uwsgi.ini`                   | `inject-uwsgi.sh`                  | Adds the 4 required flags under `[uwsgi]`                                                                                                                              |
| `gunicorn.conf.py`            | `inject-gunicorn.sh`               | Adds (or augments) a `post_fork` hook                                                                                                                                  |
| Any `.py` importing `uvicorn` | `inject-uvicorn.sh`                | Inserts a guarded import after the last top-level import                                                                                                               |
| None of the above             | `inject-startup-hook.sh`           | Creates `sitecustomize.py` and exports `PYTHONPATH` (Method 1 fallback)                                                                                                |

#### Reference scripts

{% tabs %}
{% tab title="Dispatcher" %}
Filename: `inject-sealights.sh` (dispatcher)

{% code expandable="true" %}

```bash
#!/usr/bin/env bash
# Detect server type in the current directory and run the matching injector.
# All injectors are idempotent. Pass --revert to roll back changes (.bak files).

set -euo pipefail

REVERT=false
[[ "${1:-}" == "--revert" ]] && REVERT=true

DIR="$(cd "$(dirname "$0")" && pwd)"

revert_file() {
  if [[ -f "$1.bak" ]]; then
    mv "$1.bak" "$1"
    echo "[SeaLights] Reverted $1"
  fi
}

if [[ "$REVERT" == "true" ]]; then
  for f in uwsgi.ini gunicorn.conf.py app.py main.py; do
    [[ -f "$f" ]] && revert_file "$f"
  done
  exit 0
fi

if [[ "${ENABLE_SEALIGHTS:-false}" != "true" ]]; then
  echo "[SeaLights] ENABLE_SEALIGHTS != true, skipping injection."
  exit 0
fi

if [[ -f uwsgi.ini ]]; then
  echo "[SeaLights] Detected uWSGI"
  "$DIR/inject-uwsgi.sh" uwsgi.ini
elif [[ -f gunicorn.conf.py ]]; then
  echo "[SeaLights] Detected Gunicorn"
  "$DIR/inject-gunicorn.sh" gunicorn.conf.py
elif grep -rqlE "import uvicorn|from uvicorn" --include="*.py" . 2>/dev/null; then
  ENTRY="${UVICORN_ENTRY:-app.py}"
  echo "[SeaLights] Detected Uvicorn, patching $ENTRY"
  "$DIR/inject-uvicorn.sh" "$ENTRY"
else
  echo "[SeaLights] No supported server detected. Falling back to Python startup hook."
  "$DIR/inject-startup-hook.sh"
fi
```

{% endcode %}
{% endtab %}

{% tab title="uWSGI" %}
Filename: `inject-uswgi.sh`&#x20;

{% code expandable="true" %}

```bash

#!/usr/bin/env bash
# Adds SeaLights flags to a uwsgi.ini file under [uwsgi].
set -euo pipefail
TARGET="${1:?usage: inject-uwsgi.sh path/to/uwsgi.ini}"

grep -q "# >>> SeaLights" "$TARGET" && { echo "[SeaLights] Already injected in $TARGET"; exit 0; }

cp "$TARGET" "$TARGET.bak"

python3 - "$TARGET" <<'PY'
import sys, re
path = sys.argv[1]
text = open(path).read()
snippet = """
# >>> SeaLights (auto-injected, do not edit)
enable-threads = true
single-interpreter = true
lazy-apps = true
import = python_agent.init
# <<< SeaLights
"""
if re.search(r"^\[uwsgi\]", text, re.M):
    text = re.sub(r"(^\[uwsgi\][^\n]*\n)", r"\1" + snippet, text, count=1, flags=re.M)
else:
    text += "\n[uwsgi]" + snippet
open(path, "w").write(text)
PY

echo "[SeaLights] Injected into $TARGET (backup: $TARGET.bak)"
```

{% endcode %}
{% endtab %}

{% tab title="Gunicorn" %}
Filename: `inject-gunicorn.sh`&#x20;

{% code expandable="true" %}

```bash
#!/usr/bin/env bash
# Adds a post_fork hook to gunicorn.conf.py, or augments an existing one.
set -euo pipefail
TARGET="${1:?usage: inject-gunicorn.sh path/to/gunicorn.conf.py}"

grep -q "# >>> SeaLights" "$TARGET" && { echo "[SeaLights] Already injected in $TARGET"; exit 0; }

cp "$TARGET" "$TARGET.bak"

python3 - "$TARGET" <<'PY'
import sys, re
path = sys.argv[1]
text = open(path).read()
snippet = '''
# >>> SeaLights (auto-injected, do not edit)
import os as _sl_os
def post_fork(server, worker):
    if _sl_os.getenv("ENABLE_SEALIGHTS", "false").lower() == "true":
        import python_agent.init
# <<< SeaLights
'''
if re.search(r"^def\s+post_fork\s*\(", text, re.M):
    text = re.sub(
        r"(^def\s+post_fork\s*\([^)]*\):\s*\n)",
        r"\1    import os as _sl_os\n"
        r"    if _sl_os.getenv('ENABLE_SEALIGHTS', 'false').lower() == 'true':\n"
        r"        import python_agent.init\n",
        text, count=1, flags=re.M
    )
else:
    text += snippet
open(path, "w").write(text)
PY

echo "[SeaLights] Injected into $TARGET (backup: $TARGET.bak)"
```

{% endcode %}
{% endtab %}

{% tab title="Uvicorn" %}
Filename: `inject-uvicorn.sh`&#x20;

{% code expandable="true" %}

```bash
#!/usr/bin/env bash
# Inserts a guarded `import python_agent.init` after the last top-level import.
set -euo pipefail
TARGET="${1:?usage: inject-uvicorn.sh path/to/app.py}"
[[ "$TARGET" == *.py && -f "$TARGET" ]] || { echo "Error: provide a valid .py file"; exit 1; }

grep -q "# >>> SeaLights" "$TARGET" && { echo "[SeaLights] Already injected in $TARGET"; exit 0; }

cp "$TARGET" "$TARGET.bak"

python3 - "$TARGET" <<'PY'
import sys, re
path = sys.argv[1]
src = open(path).read().splitlines(keepends=True)

last_import = 0
for i, line in enumerate(src):
    if re.match(r'^\s*(import |from )\S', line):
        last_import = i + 1

snippet = (
    "\n"
    "# >>> SeaLights (auto-injected, do not edit)\n"
    "import os as _sl_os\n"
    "if _sl_os.getenv('ENABLE_SEALIGHTS', 'false').lower() == 'true':\n"
    "    import python_agent.init\n"
    "# <<< SeaLights\n"
    "\n"
)

src.insert(last_import, snippet)
open(path, "w").writelines(src)
PY

echo "[SeaLights] Injected into $TARGET (backup: $TARGET.bak)"
```

{% endcode %}
{% endtab %}

{% tab title="Startup-hook" %}
Filename: `inject-startup-hook.sh`

```bash
#!/usr/bin/env bash
# Creates a sitecustomize.py and exports PYTHONPATH.
# Source this script (do not exec) so the env var change persists.
set -euo pipefail

HOOK_DIR="${SEALIGHTS_HOOK_DIR:-/opt/python-startup}"
mkdir -p "$HOOK_DIR"
echo "import python_agent.init" > "$HOOK_DIR/sitecustomize.py"
export PYTHONPATH="$HOOK_DIR:${PYTHONPATH:-}"
echo "[SeaLights] Startup hook ready at $HOOK_DIR (PYTHONPATH updated)"
```

{% endtab %}
{% endtabs %}

#### Usage in CI

{% tabs %}
{% tab title="Bash" %}

```bash
if [[ "${ENABLE_SEALIGHTS:-false}" == "true" ]]; then
  ./ci/inject-sealights.sh
  trap './ci/inject-sealights.sh --revert' EXIT
fi
python server.py
```

{% endtab %}

{% tab title="GitHub Actions" %}

<pre class="language-yaml"><code class="lang-yaml">- name: Inject SeaLights instrumentation
<strong>  if: env.ENABLE_SEALIGHTS == 'true'
</strong>  run: |
    chmod +x ci/inject-*.sh
    ci/inject-sealights.sh

- name: Run application
  run: python server.py     # or your usual launch command

- name: Revert injected files
  if: always() &#x26;&#x26; env.ENABLE_SEALIGHTS == 'true'
  run: ci/inject-sealights.sh --revert
</code></pre>

{% endtab %}

{% tab title="PowerShell" %}

```powershell
if ($env:ENABLE_SEALIGHTS -eq 'true') {
  bash ./ci/inject-sealights.sh
  try { python server.py } finally { bash ./ci/inject-sealights.sh --revert }
} else {
  python server.py
}
```

{% endtab %}

{% tab title="Dockerfile " %}
Build-time injection — the image is purpose-built for an instrumented run, vanilla images are untouched

```dockerfile
FROM python:3.9-slim
ARG ENABLE_SEALIGHTS=false
ENV ENABLE_SEALIGHTS=${ENABLE_SEALIGHTS}

COPY . /app
WORKDIR /app
COPY ci/inject-*.sh /usr/local/bin/

RUN if [ "$ENABLE_SEALIGHTS" = "true" ]; then \
      chmod +x /usr/local/bin/inject-*.sh && \
      pip install sealights-python-agent && \
      /usr/local/bin/inject-sealights.sh ; \
    fi

ENTRYPOINT ["python", "server.py"]
```

{% endtab %}

{% tab title="Kubernetes " %}
The init-container step injects the mounted source.

```yaml
spec:
  volumes:
    - name: app-source
      emptyDir: {}
    - name: injectors
      configMap:
        name: sealights-injectors
        defaultMode: 0755
  initContainers:
    - name: prepare-source
      image: myapp:base
      command: ['sh', '-c', 'cp -r /app/. /work/']
      volumeMounts:
        - { name: app-source, mountPath: /work }
    - name: inject-sealights
      image: python:3.9-slim
      env:
        - { name: ENABLE_SEALIGHTS, value: "true" }
      command: ['sh', '-c', 'cd /work && pip install sealights-python-agent && /injectors/inject-sealights.sh']
      volumeMounts:
        - { name: app-source, mountPath: /work }
        - { name: injectors,  mountPath: /injectors }
  containers:
    - name: app
      image: myapp:base
      command: ['python', '/work/server.py']
      volumeMounts:
        - { name: app-source, mountPath: /work }
```

(The injector scripts live in a `ConfigMap`, the source is copied to a shared `emptyDir`, and only that copy gets patched. The image itself stays untouched.)
{% endtab %}
{% endtabs %}

### Troubleshooting

| Symptom                                                               | Likely cause                                                                   | Fix                                                                                            |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| No coverage events at all                                             | Agent never imported                                                           | Add a temporary `print("SL loaded")` next to `import python_agent.init` and verify it executes |
| Coverage only from one worker, not all                                | uWSGI missing `--lazy-apps`, or Gunicorn missing `post_fork` hook              | Re-run the Method 4 injector, or add the flag/hook manually                                    |
| `FileNotFoundError: sltoken.txt`                                      | Token file not in the working directory                                        | Set `SL_TOKEN_FILE` to its absolute path                                                       |
| `sl-python: not found` in container                                   | `pip install` ran in a different image stage than runtime                      | Install in the same stage, or use the init-container pattern in Method 2                       |
| `ENABLE_SEALIGHTS=True` doesn't activate the hook                     | Case-sensitive comparison                                                      | All snippets here use `.lower() == "true"` — make sure custom code does the same               |
| Method 4 injector ran twice and second run printed "Already injected" | Expected — injectors are idempotent                                            | No action needed                                                                               |
| `--revert` left `.bak` files behind                                   | Revert moves `.bak` → original; if you ran it twice, the second run is a no-op | Check git status; the original is back in place                                                |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sealights.io/knowledgebase/setup-and-configuration/sealights-agents-and-plugins/python-agent/capturing-coverage-from-runtime-application/running-backend-server-using-sealights-agent.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
