This is not the install path. The supported install is
uvx openoutreach find 10(orpip install openoutreach) — see the README quick start. This page is for running it unattended on a server, which is the one job the image still does better than a shell. Development and tests run natively; there is nodocker-test.
There is no daemon any more. The work verb is bounded by a goal, so the container runs one job and exits —
docker run … 10 emailsmeans ten more leads with addresses, then stop. Nothing here loops; whatever schedules the container (a systemd timer, a cron entry) decides how often, which is the right place for that decision. A container that ends is also a container you can see fail.
The image is a slim Python runtime with no browser and no VNC — a venv at /opt/venv holding the
installed package, and nothing from the build stage.
Pre-built images are published to GitHub Container Registry.
docker run --pull always -it -v ~/.openoutreach/data:/app/data \
ghcr.io/eracle/openoutreach:latest 10 emails > leads.csv- The arguments are the goal. Anything after the image name goes to
find, so10 emailsis ten more leads carrying an address; with none given it finds one. The CSV lands on stdout — redirect it, and note that it carries the whole campaign, so the newest file supersedes the last. -itis only needed for the interactive onboarding on first run — product/objective → LLM key → BetterContact key → your email → country → newsletter/legal. Configure those by environment instead and the container needs no TTY at all. Do not pass-twhen redirecting: a TTY makes stdout and stderr the same stream, and the CSV would arrive with the logs mixed into it.-v ~/.openoutreach/data:/app/datapersists everything (CRM database, model blobs, embeddings) on your host across restarts. The image setsOPENOUTREACH_DB=/app/data/db.sqlite3.
There are no ports to publish — there is no web server of its own and no browser to watch. (To browse your CRM, run the Django Admin separately; see below.)
| Tag | Description |
|---|---|
latest |
Latest published build |
sha-<commit> |
Pinned to a specific commit |
1.0.0 / 1.0 |
Semantic version (when tagged) |
The container exits when its goal is met, so "keep it running" is a scheduling question and belongs to whatever schedules things on that host. A systemd timer firing hourly:
# openoutreach.service — pair with a .timer of your choosing
[Service]
Type=oneshot
ExecStart=/usr/bin/docker run --rm -v /srv/openoutreach:/app/data \
ghcr.io/eracle/openoutreach:latest 10 emails
StandardOutput=append:/srv/openoutreach/leads.csvExit 0 means the goal was met; non-zero means it stopped short and said why on stderr, which the
journal will have. Stopping it is docker stop, and data persists in the mounted directory —
the number you ask for is more than you already have, so the next run continues rather than
restarting.
The image runs a job, not a web server. To browse Leads and Deals, run the admin server (locally or in a second container) and publish port 8000:
docker run --pull always -it -p 8000:8000 -v ~/.openoutreach/data:/app/data \
ghcr.io/eracle/openoutreach:latest openoutreach runserver 0.0.0.0:8000Then open http://localhost:8000/admin/ (create a superuser first with
openoutreach createsuperuser).
local.yml builds the same production image from a checkout. It is what the prod VM uses — one
directory per operator, each with its own CRM.
git clone https://github.com/eracle/OpenOutreach.git
cd OpenOutreach
# Build and start
make upThe code is what the image was built from. Only ./data is mounted, so moving an instance
forward is git pull + rebuild — never a live edit inside the container.
Note: The compose file uses HOST_UID / HOST_GID environment variables (defaulting to 1000)
for file ownership. If your host UID differs from 1000, set them explicitly:
HOST_UID=$(id -u) HOST_GID=$(id -g) make up| Command | Description |
|---|---|
make build |
Build the Docker image without starting |
make up |
Build and run one job |
make stop |
Stop the running containers |
make logs |
Follow application logs |
To run against a database file you already have, bind-mount the host directory containing it onto
/app/data (the image opens /app/data/db.sqlite3):
docker run --pull always -v ~/.openoutreach/data:/app/data ghcr.io/eracle/openoutreach:latestPlace your db.sqlite3 inside the mounted directory (~/.openoutreach/data/ above; swap for your own
path). Two caveats: the dir and file must be writable by uid 1000 (the container user) or writes fail
with readonly database; and find applies migrations on the way in, so back the file up first
(cp db.sqlite3{,.bak}) if it's precious.
Note that a native install uses the same default path, so uvx openoutreach and the container can
be pointed at one CRM — but never at the same time. One job per database, ever: SQLite's WAL lets
status read alongside a running job, not two jobs write.