Skip to main content
Version: v5

Get started

You need Docker, and you need nowhere to put anything yet — the app names its own store file inside the directory you give it.

Run

docker run -d \
--name suivi-bourse \
--restart unless-stopped \
-v suivi-bourse:/data \
-p 8080:8080 \
ghcr.io/pbrissaud/suivi-bourse:5

--restart unless-stopped is what brings the container back after a reboot — and after a crash. Nothing inside the container restarts anything: the app is one process, so when it stops the container exits and this policy is what decides whether it comes back.

The -v is the whole difference between a trial and an install. Without it the container still starts — it simply keeps nothing, and it says so on every start, in its first log lines and on the Settings page of the app. That is a fair way to try the app for an evening. It is not a way to keep a portfolio: the store lives in the container's writable layer, and the first docker rm takes it.

Open

Go to http://localhost:8080.

On a store nobody has answered for yet, the app opens on a short walk of three passages. It says in three sentences what it is — a ledger of dated events, quotes it fetches itself, one currency everything is reported in — and, on an install with no volume, that this container keeps nothing.

  1. The required settings. Exactly one thing has to be decided at installation: the base currency every figure is reported in. It has no default, which is why it is asked at all.
  2. Your accounts. Every event is recorded on an account, and your install already owns one — so this passage shows you what you have and asks for nothing. You declare others, and rename this one, on the Accounts page. It comes before the next passage on purpose: a file you import names its accounts by their identifier.
  3. Your first events. Two doors, side by side and neither recommended over the other: hand the app a .csv or an .xlsx, or type a first purchase. Typing a position is creating dated events, so a reader with no file to import has not missed anything.

Walking through is not answering. No passage extracts anything: you can go through all three, supply nothing, and end up on a working app. And you may leave at any point — the modal closes on its cross, with no Later button, because the way out should not have the weight of the answer. Whichever way you leave, your browser remembers that you have been through: the walk does not come back on the next page load, and emptying your ledger later does not bring it back either. Nothing about it is recorded in your store, so a store you wipe asks the question again.

The currency may be answered later, and where you left it the app says so. While your ledger is empty the answer is still yours to change; from your first event it is fixed, because changing it afterwards would reinterpret amounts you already recorded. Until it is answered, prices are still fetched, nothing is converted and no return is computed; the app says so on screen rather than refusing.

If you would rather not answer it by hand, the same question is answered with one curl on the write API, which is served on the same port as the page. An import that declares its own currency answers it too, and then even that call is unnecessary.

Then record a position, or import the files you already have. Prices appear as they are fetched; the history behind them is rebuilt backwards while you use the app. Give it time rather than reloading: about 25 minutes for 30 symbols over five years, and figures that depend on a price you do not have yet say so instead of guessing.

Is it working?

The image carries its own probe, and the app answers it on /health, on the same port as everything else. That one answer speaks to two readers at once, and they never mix.

The status code is for whatever supervises the container. It answers one question — should this container be restarted — and 200 means the process is serving and its store answers. A 503 means it cannot do its work at all, which is the only failure a restart repairs. That is what docker ps reads to mark the container healthy, and what a Kubernetes probe would read.

The body is for you. It names each of the three jobs — the scrape, the rebuild, the returns — with the last time it ran and what came of that pass, plus one word for the whole:

curl -s localhost:8080/health
{
"status": "attention",
"now": "2026-08-24T15:00:00+00:00",
"scheduler_running": true,
"jobs": {
"scrape": {
"status": "attention", "at": "2026-08-24T14:59:12+00:00",
"verdict": "frozen", "held": 12, "attention": ["ASML.AS"]
},
"backfill": {
"status": "ok", "at": "2026-08-24T14:58:40+00:00",
"verdict": "running", "complete": 7, "in_scope": 12, "attention": []
},
"performance": {
"status": "ok", "at": "2026-08-24T14:58:41+00:00",
"verdict": "ran", "error": null
}
}
}

Each job — and the whole above them — carries one of three words. ok is everything ran and nothing asks to be looked at. attention is something ran badly, or has stopped running. unknown is nothing has been observed yet, which is what a container that started a minute ago honestly says, and which is deliberately not ok. attention lists the securities the verdict is about, so you know which line to go and read; verdict is the job's own word for its last pass, and at is when that pass happened.

The bell at the top right of the app reads this same body, so you do not have to curl anything to know. It carries two channels at once: its icon takes the colour — green is ok, amber is attention or a rebuild still running, red is the app not answering at all — and its badge counts everything still open. It opens onto a panel where the health, the facts about this installation and the advisories about your portfolio sit together, and from there onto the Settings page, where the jobs and the store are written out in full.

A scrape that has gone quiet is amber, and the code stays 200

Restarting the container repairs nothing that Yahoo or the market broke, and a probe that failed on a stuck job would turn it into a restart loop that fixes nothing and hides everything. So a job that is late, wedged or silent is read in the body and never in the status code. The one thing that makes the code fail is the store not answering — and then there is no body at all, which is itself the answer.

Backups come in two forms, and neither is hot

Exporting your events backs up the portfolio: re-import them into a fresh install and the rebuild fetches the prices again. Stopping the container, copying the volume and restarting backs up the installation — the only form that also keeps your settings, your base currency and the fine-grained price history you have accumulated. That last one is the thing no late backup ever recovers: past 60 days, nothing sells you a price finer than hourly.


Running version 4 today? Nothing here is an upgrade, and the reconciliation is its own page: Coming from v4.