---
name: datatailr-deploy-service
description: Deploy a long-running service on the Datatailr platform using the Python SDK. Services are background processes like API servers, message consumers, or workers that auto-restart on exit. Use when the user asks to deploy a service, API, or background worker on Datatailr, or to make existing service code run on Datatailr / dt.
---

# Deploy a Service on Datatailr

## Overview

A Datatailr service is a long-running background process (API server, message consumer, worker). It auto-restarts if it exits. The `datatailr` Python SDK handles packaging, image building, and scheduling. The SDK is already installed in the workspace -- do not look for it.

## Project layout

The deploy script must live **outside** the service module directory; it is only used locally to register and run the service. Do not use hyphens in directory names -- use underscores. Always prefer **absolute imports** (`from my_service.utils import ...`) instead of relative imports (`from .utils import ...`).

**Only the service package directory** (e.g. `my_service/`) is packaged and sent to the remote. Root-level files such as `deploy.py`, a root `setup.py`, or root `requirements.txt` are not included. Therefore **all files required for the job** (service code, `setup.py`, `pyproject.toml`, Cython sources, data files, etc.) must live **inside** the service package directory. For installable packages (e.g. with Cython), put `setup.py` and any build assets inside that directory.

```
my_project/
├── my_service/
│   ├── __init__.py
│   ├── app.py        # Service code
│   └── setup.py      # If needed; must be inside my_service/
└── deploy.py         # Deploy script (outside my_service/; not sent to remote)
```

## Service code

Write a module with a `main(port)` function that starts the service. The platform calls `main` with the assigned port.

```python
# my_service/app.py
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route("/")
def index():
    return jsonify({"status": "running"})

@app.route("/__health_check__.html")
def health_check():
    return "OK\n"

def main(port):
    app.run("0.0.0.0", port=int(port), debug=False)

if __name__ == "__main__":
    main(1024)
```

Key points:
- `main(port)` is required -- the platform passes the port to bind to.
- Include a `/__health_check__.html` endpoint returning `"OK"` for platform health monitoring.
- The service can use any HTTP framework (Flask, FastAPI, etc.) or no framework at all.

## Deploy script

Import the `main` function (not the module) and use the `Service` class. No `framework` parameter.

```python
# deploy.py
from my_service.app import main
from datatailr import Service, Resources

service = Service(
    name="My Service",
    entrypoint=main,
    resources=Resources(memory="2g", cpu=1),
    python_requirements=["flask"],
)
service.run()
```

Key parameters:
- `name`: human-readable name on the platform.
- `entrypoint`: the `main` function (imported callable, not a string).
- `resources`: CPU and memory for the container. Defaults to 128MB / 0.25 CPU.
- `python_requirements`: a list of pip package names (e.g. `["flask"]`) or a path to a `requirements.txt` file. The path is resolved **locally** when the job definition is built (at deploy time); the file is read on the machine running the deploy script and its **contents** are sent to the remote. If using a path, the file must exist where you run `python deploy.py` (e.g. `my_service/requirements.txt` when running from project root).
- `service.run()` saves the definition AND starts it. Use `service.save()` to only save.
- `python_version`: Python version for the container image (default `"auto"` -- auto-detects from your environment).
- `build_script_pre` / `build_script_post`: shell commands for the Docker image build. See [datatailr-build-scripts](../datatailr-build-scripts/SKILL.md) for formatting. If the script must operate on the installed package, it is at `/opt/datatailr/usr/lib/python/site-packages/<package_name>` where `<package_name>` is the name of the directory that is deployed (e.g. `my_service`).
- `env_vars`: dict of environment variables passed to the running container.
- `run_as` / `acl`: leave unset to use default permissions. Only if the user explicitly requests access restrictions, see [datatailr-acl.md](../datatailr-acl.md). If their request is vague (e.g., "restrict access"), ask who should be able to see, edit, and operate the job before proceeding.

## Build environment and native dependencies

Datatailr jobs run in **Linux (Debian) containers**. The user's local OS (macOS, Windows, etc.) is **irrelevant** to the build.

**If the service uses Cython, C extensions, native libraries, or any dependency that requires a compiler or system headers, you MUST read [datatailr-build-scripts](../datatailr-build-scripts/SKILL.md) before writing the deploy script.** The build image is minimal and has no compiler or build tools — they must be installed explicitly via `build_script_pre`. Do not assume they are present and do not skip them based on a deploy command succeeding (it may have used cached layers).

## Deploying

After creating all the files, tell the user to activate their venv (if using one) and run the deploy script; if possible, give them the exact command to run. Do not mention `PATH`, `PYTHONPATH`, venv bin paths, or any internal details. Do not print the full app code at the end.
When you (the agent) run the deploy script yourself, you may get `RuntimeError: Datatailr CLI ('dt') is not available.` or an `ImportError` for `datatailr`. If user is using a venv (ask if they are), you need to invoke the deploy script as `PATH="/path/to/.venv/bin:$PATH" .venv/bin/python deploy_script.py` so that `dt` is on `PATH`. If `dt` is also not installed in the venv, tell the user to run `datatailr setup-cli`. Do not run `setup-cli` yourself as it's an interactive command which requires input from the user.

## Calling this service from other jobs

Once deployed, the service is reachable from any other Datatailr job (app, workflow, add-in, or another service) at `http://<normalized-name>`. The name is derived from the `name` parameter: lowercased, non-alphanumeric characters replaced with hyphens (e.g., `"My Service"` becomes `http://my-service`).

For full details on constructing URLs, authentication patterns, and naming conventions, see [service-communication.md](../service-communication.md).

## Checklist

- [ ] Service has a `main(port)` function
- [ ] Service has a `/__health_check__.html` endpoint returning `"OK"`
- [ ] Deploy script is outside the service module directory
- [ ] Deploy script imports the `main` function (not a string path)
- [ ] All files required for the job are inside the service package directory
- [ ] `python_requirements` includes all dependencies
- [ ] If native code / Cython / system libraries are needed: read [datatailr-build-scripts](../datatailr-build-scripts/SKILL.md) and set `build_script_pre` / `build_script_post`
- [ ] No hyphens in directory names (use underscores)

## Platform environment variables

The platform sets these environment variables in every running service container. Use them when the service needs to know its runtime context:

- `DATATAILR_JOB_NAME`: name of the running service.
- `DATATAILR_USER`: username running the service.
- `DATATAILR_JOB_TYPE`: always `service` for services.
- `DATATAILR_JOB_ENVIRONMENT`: Datatailr compute environment (`dev`, `pre`, or `prod`).
- `DATATAILR_DOMAIN`: platform domain (useful for constructing URLs).
