Skip to main content
Version: v4

Docker Compose

Docker Compose is the recommended way to run SuiviBourse. It deploys the full stack:

  • suivi-bourse-app — the application
  • suivi-bourse-influxdb — a pre-initialized InfluxDB 3 Core instance
  • suivi-bourse-graf — a Grafana with a provisioned datasource and dashboard

Two files are yours and yours only: .env (all settings) and a config directory (your portfolio). Both are git-ignored, and neither docker-compose.yaml nor any other shipped file needs editing — including to switch modes.

Requirements

  • Windows, Linux or macOS
  • Docker (> 19.03)
  • Docker Compose

Steps

  1. Download the source code from the latest release and extract it.

  2. Move into the docker-compose folder.

  3. Create your .env and config directory from the shipped templates:

make init

make init creates .env (with a freshly generated INFLUXDB_TOKEN) and data/, and skips whatever already exists — re-running it never overwrites your configuration. Without make, copy the templates yourself, taking care not to clobber an existing .env or data/, and set INFLUXDB_TOKEN by hand.

  1. Configure your portfolio using one of the two modes:

Edit data/config.yaml following the manual mode doc:

data/config.yaml
shares:
- name: Apple
symbol: AAPL
purchase:
quantity: 1
fee: 2
cost_price: 119.98
estate:
quantity: 2
received_dividend: 2.85

This is the default: with no event files around, the app runs in manual mode.

  1. Start the stack:
docker compose up -d # or: make up

Ports

Every port is configurable in .env.

ServiceVariableDefaultPurpose
GrafanaGRAFANA_PORT3000Dashboards (login admin / admin)
InfluxDB 3INFLUXDB_PORT8181Database HTTP API
AppSB_METRICS_PORT8081Legacy Prometheus /metrics endpoint

Not publishing them at all

Publishing is an overlay, not part of the stack itself. docker-compose.yaml maps no port — the three services talk to each other over the compose network — and docker-compose.expose.yaml is what puts them on the host. .env.example chains the two, so nothing changes for a local stack:

.env
COMPOSE_FILE=docker-compose.yaml:docker-compose.expose.yaml

Comment that line out and docker compose up -d starts the same stack with nothing bound on the host. That is what you want behind a reverse proxy, which reaches Grafana over the compose network instead — see Deploying with Coolify. The port variables above keep working either way; they are read by the overlay.

Nothing else has to change: the compose commands stay docker compose up -d, docker compose logs -f app, and so on. Compose reads COMPOSE_FILE from .env and chains the files itself. On Windows, separate them with ; instead of :.

The overlay is also the place to publish less: keep only the grafana block if the dashboards are all you reach from outside, and leave InfluxDB and /metrics on the internal network.

Configuration directory

The stack mounts one directory read-only at /home/appuser/.config/SuiviBourse inside the app container:

data/
├── settings.yaml # Mode override, events options, opt-in accounts
├── config.yaml # Manual mode: your portfolio
└── events/ # Events mode: your broker exports (.csv / .xlsx)

Point the stack somewhere else — a NAS share, a directory outside the release folder — with SB_CONFIG_DIR in .env:

.env
SB_CONFIG_DIR=/srv/suivi-bourse/config

Files for the mode you are not using are simply ignored, so you can keep a config.yaml around while running in events mode.

Environment variables

Everything lives in .env, which is created from .env.example and never overwritten by an upgrade. All values are optional except INFLUXDB_TOKEN, and the names match the application's own variables one-for-one.

Deployment

VariableDefaultDescription
SB_VERSION4App image tag. Pin an exact version (4.2.1) for reproducible upgrades and rollbacks
SB_CONFIG_DIR./dataConfig directory mounted into the app
COMPOSE_PROJECT_NAMEsuivi-boursePrefix for container and volume names — change it to run two stacks side by side
GF_ADMIN_PASSWORDadminGrafana admin password

Application

VariableDefaultDescription
SB_CONFIG_MODE(empty)Force manual or events. Empty leaves it to settings.yaml, then auto-detection
SB_REGULAR_INTERVAL120Poll interval (seconds) for a symbol whose market is open
SB_PERF_INTERVAL120Performance recompute interval (seconds)
SB_INGESTION_INTERVAL300Event ingestion interval (seconds)
SB_STALENESS_HORIZON900Price-freshness sonde horizon (seconds); 0 disables it
SB_DYNAMIC_EXECUTOR_POOLfalseAuto-size the scheduler thread pool
SB_EXECUTOR_POOL10Fixed thread-pool size when auto-sizing is off
SB_BACKFILL_INTERVAL60Backfill check interval (seconds)
SB_BACKFILL_DELAY10Delay between backfill requests (seconds)
SB_BACKFILL_CHUNK_DAYS365Days of history per backfill request
SB_PROMETHEUS_ENABLEDtrueExpose the legacy Prometheus /metrics endpoint
SB_METRICS_PORT8081Port for /metrics
INFLUXDB_DATABASEsuivi_bourseInfluxDB database name
INFLUXDB_TOKEN(none)InfluxDB admin token — required, generated by make init
LOG_LEVELINFOLogging level
InfluxDB token

No token ships with the project: .env.example leaves INFLUXDB_TOKEN empty and make init fills it with a freshly generated value, so every deployment gets its own. The stack refuses to start while it is empty.

If you created .env by hand, or want to rotate the token:

make token # prints a fresh token
# paste it into INFLUXDB_TOKEN in .env, then:
docker compose up -d

InfluxDB, Grafana's provisioned datasource and the app all read that one value. Avoid $ in the token: Grafana expands $VAR inside provisioning files.

File structure

After setup, your docker-compose folder looks like:

docker-compose/
├── .env # Yours — all settings (git-ignored)
├── .env.example # Shipped template
├── data/ # Yours — portfolio config (git-ignored)
│ ├── settings.yaml
│ ├── config.yaml # Manual mode
│ └── events/ # Events mode
│ └── 2024.csv
├── data.example/ # Shipped template
├── examples/
│ └── events-example.csv # Sample transaction file
├── Makefile # init / up / logs / upgrade helpers
├── docker-compose.yaml # The stack — publishes no port
├── docker-compose.expose.yaml # Overlay: publishes them on the host
├── influxdb3-init.sh # InfluxDB 3 bootstrap (creates the database)
└── grafana_provisioning/
├── datasources/ # InfluxDB SQL datasource
└── dashboards/ # "Stock share monitoring" dashboard

The same layout works in both modes — there is no mode-specific file structure.

Upgrading

SB_VERSION pins the app image, so upgrades are explicit and reversible:

docker compose pull && docker compose up -d # or: make upgrade

Bump SB_VERSION in .env to move across majors, or set it back to the previous version to roll back. Your .env and config directory are untouched by an upgrade, so re-extracting a newer release over the folder is safe.

Ports moved to an overlay

docker-compose.yaml no longer publishes anything by itself — an .env written before that change has no COMPOSE_FILE line, and the stack then comes up with Grafana unreachable on localhost:3000. Add the line to your existing .env:

.env
COMPOSE_FILE=docker-compose.yaml:docker-compose.expose.yaml

make init prints the same reminder when it finds an .env without it. See Not publishing them at all for why the split exists.

Migrating from a pre-4.2 layout

Earlier stacks kept config.yaml, settings.yaml and events/ at the root of the docker-compose folder, mounted file by file, with the token duplicated in three places. To move over:

cd docker-compose
mkdir -p data
mv config.yaml settings.yaml data/ 2>/dev/null
mv events data/ 2>/dev/null
cp .env.example .env.new # then port your values over, see below

Three things to know:

  • .env is no longer shipped, so it will not be overwritten again — but the old one was tracked, and a git pull may remove it. Keep a copy before upgrading.
  • Variable names now match the app's: SCRAPING_INTERVAL becomes SB_REGULAR_INTERVAL, INGESTION_INTERVAL becomes SB_INGESTION_INTERVAL, and so on. The old SCRAPING_INTERVAL mapped to the deprecated SB_SCRAPING_INTERVAL, which logged a warning on every boot.
  • influxdb3-token.json is gone. The token comes from INFLUXDB_TOKEN in .env alone; the file is generated inside the container at startup.

To keep the old flat layout instead, set SB_CONFIG_DIR=. in .env — the whole docker-compose folder then becomes the config directory.

Deploying with Coolify

Coolify deploys the stack straight from the repository, and most of it fits without changes: every setting is a ${VAR} in docker-compose.yaml, so Coolify surfaces them as editable environment variables, and the InfluxDB / Grafana named volumes are handled natively. There is no .env to provide — Coolify injects the variables itself.

Ports take care of themselves. Coolify regenerates its deployment from docker-compose.yaml alone and writes its own environment, so it never sees the COMPOSE_FILE line from .env.example and never loads docker-compose.expose.yaml. What it deploys is the unpublished stack: attach a domain to the grafana service and its proxy reaches the container over the compose network, with nothing bound on the host. Same reasoning for any other reverse-proxied deployment — Dokploy, a hand-written Traefik or Caddy setup.

One thing needs care: the configuration directory. Coolify recreates the repository clone on every deployment, data/ is git-ignored so it is never in that clone, and make init does not run. Left at its ./data default, the app would start against an empty directory and find no portfolio.

Point SB_CONFIG_DIR at an absolute path on the host instead:

  1. Create a new Docker Compose resource pointing at this repository, with docker-compose/docker-compose.yaml as the compose file.

  2. Set at least these environment variables in Coolify:

    INFLUXDB_TOKEN=apiv3_... # required — generate one, see below
    SB_CONFIG_DIR=/data/suivi-bourse/config

    Generate the token with openssl rand -hex 32 and prefix it with apiv3_. The stack refuses to start while INFLUXDB_TOKEN is empty.

  3. Create that directory on the Coolify host and put your configuration in it — settings.yaml plus either config.yaml or an events/ folder, exactly the layout described in Configuration directory. You can also declare it through Coolify's Persistent Storage as a bind mount.

  4. Deploy.

An absolute path is not just a workaround: it keeps your portfolio outside the deployment's working directory, so redeployments never touch it. Everything else — upgrades via SB_VERSION, mode auto-detection, adding event files — works the same as anywhere else.

Publishing a port anyway

If you do want a port bound on the Coolify host — reaching the InfluxDB API from outside, say — add the mapping in Coolify's compose editor for that service. Pointing the resource at docker-compose.expose.yaml instead is not an option: the overlay only carries ports: blocks and is not a stack on its own.

is_directory belongs in a Coolify-only file

Coolify understands a non-standard is_directory: true flag on bind mounts that makes it pre-create the host directory. Do not add it to docker-compose.yaml: plain Docker Compose rejects it outright with additional properties 'is_directory' not allowed, which would break every other deployment. Keep it in a separate compose file if you want it.

Development stack

A docker-compose.dev.yaml is also provided. It builds the app image locally, runs InfluxDB without authentication, enables DEBUG logging, and also serves this documentation site on port 4000. It defaults to the checked-in data.example/ directory, so it runs with no setup:

docker compose -f docker-compose.dev.yaml up -d # or: make dev

Point it at your own config with SB_CONFIG_DIR=./data.