Docker Compose
Docker Compose is the recommended way to run SuiviBourse. It deploys the full stack:
suivi-bourse-app— the applicationsuivi-bourse-influxdb— a pre-initialized InfluxDB 3 Core instancesuivi-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
-
Download the source code from the latest release and extract it.
-
Move into the
docker-composefolder. -
Create your
.envand 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.
- Configure your portfolio using one of the two modes:
- Manual mode
- Events mode
Edit data/config.yaml following the manual mode doc:
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.
Drop your transaction files into data/events/:
date,event_type,symbol,name,quantity,unit_price,fee,amount,notes
2024-01-15,BUY,AAPL,Apple Inc,10,150.00,2.50,,Initial purchase
2024-03-01,DIVIDEND,AAPL,Apple Inc,,,,8.50,Q1 2024
That's the whole switch. SuiviBourse detects the event files and runs in
events mode — no .env, settings.yaml or compose change required. Copy
examples/events-example.csv to get started, and see the
events mode doc for the full CSV format and
backfill for historical data.
Set SB_CONFIG_MODE=events in .env only if you want the mode pinned
regardless of directory contents.
- Start the stack:
docker compose up -d # or: make up
Ports
Every port is configurable in .env.
| Service | Variable | Default | Purpose |
|---|---|---|---|
| Grafana | GRAFANA_PORT | 3000 | Dashboards (login admin / admin) |
| InfluxDB 3 | INFLUXDB_PORT | 8181 | Database HTTP API |
| App | SB_METRICS_PORT | 8081 | Legacy 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:
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:
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
| Variable | Default | Description |
|---|---|---|
SB_VERSION | 4 | App image tag. Pin an exact version (4.2.1) for reproducible upgrades and rollbacks |
SB_CONFIG_DIR | ./data | Config directory mounted into the app |
COMPOSE_PROJECT_NAME | suivi-bourse | Prefix for container and volume names — change it to run two stacks side by side |
GF_ADMIN_PASSWORD | admin | Grafana admin password |
Application
| Variable | Default | Description |
|---|---|---|
SB_CONFIG_MODE | (empty) | Force manual or events. Empty leaves it to settings.yaml, then auto-detection |
SB_REGULAR_INTERVAL | 120 | Poll interval (seconds) for a symbol whose market is open |
SB_PERF_INTERVAL | 120 | Performance recompute interval (seconds) |
SB_INGESTION_INTERVAL | 300 | Event ingestion interval (seconds) |
SB_STALENESS_HORIZON | 900 | Price-freshness sonde horizon (seconds); 0 disables it |
SB_DYNAMIC_EXECUTOR_POOL | false | Auto-size the scheduler thread pool |
SB_EXECUTOR_POOL | 10 | Fixed thread-pool size when auto-sizing is off |
SB_BACKFILL_INTERVAL | 60 | Backfill check interval (seconds) |
SB_BACKFILL_DELAY | 10 | Delay between backfill requests (seconds) |
SB_BACKFILL_CHUNK_DAYS | 365 | Days of history per backfill request |
SB_PROMETHEUS_ENABLED | true | Expose the legacy Prometheus /metrics endpoint |
SB_METRICS_PORT | 8081 | Port for /metrics |
INFLUXDB_DATABASE | suivi_bourse | InfluxDB database name |
INFLUXDB_TOKEN | (none) | InfluxDB admin token — required, generated by make init |
LOG_LEVEL | INFO | Logging level |
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.
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:
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:
.envis no longer shipped, so it will not be overwritten again — but the old one was tracked, and agit pullmay remove it. Keep a copy before upgrading.- Variable names now match the app's:
SCRAPING_INTERVALbecomesSB_REGULAR_INTERVAL,INGESTION_INTERVALbecomesSB_INGESTION_INTERVAL, and so on. The oldSCRAPING_INTERVALmapped to the deprecatedSB_SCRAPING_INTERVAL, which logged a warning on every boot. influxdb3-token.jsonis gone. The token comes fromINFLUXDB_TOKENin.envalone; 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:
-
Create a new Docker Compose resource pointing at this repository, with
docker-compose/docker-compose.yamlas the compose file. -
Set at least these environment variables in Coolify:
INFLUXDB_TOKEN=apiv3_... # required — generate one, see belowSB_CONFIG_DIR=/data/suivi-bourse/configGenerate the token with
openssl rand -hex 32and prefix it withapiv3_. The stack refuses to start whileINFLUXDB_TOKENis empty. -
Create that directory on the Coolify host and put your configuration in it —
settings.yamlplus eitherconfig.yamlor anevents/folder, exactly the layout described in Configuration directory. You can also declare it through Coolify's Persistent Storage as a bind mount. -
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.
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 fileCoolify 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.