Starting the lab is like turning on the lights in a three-story building. demarrer flips the main switch, then a caretaker walks up to each floor to check that the lights are really on before handing you the keys: that caretaker is Docker Compose's healthchecks. Kibana is not even allowed to start before Elasticsearch has been declared healthy. Then importer fills Elasticsearch's shelves, charger-graphe fills Neo4j's, and etat gives you the building's floor plan: who is there, who answers, how many documents in each index. The rest of the lesson is a guided tour of the two rooms where you will spend the course: Kibana's Dev Tools console and Neo4j Browser.
demarrer chains three docker compose steps: pull (downloads the images, ~6 GB the first time, nothing afterward), up -d (creates and launches the containers in the background), then a wait loop that queries docker inspect every 3 seconds until each container is healthy.
The word healthy comes from docker-compose.yml. Each service declares a test there that Docker replays regularly:
| Service | Test run inside the container | Frequency |
|---|---|---|
| elasticsearch | curl -fsS http://localhost:9200/_cluster/health must contain "status":"green" or "yellow" | every 10 s, 30 attempts, 30 s grace period at startup |
| kibana | curl -fsS http://localhost:5601/api/status must contain "level":"available" | every 10 s, 30 attempts, 40 s grace period |
| neo4j | wget -qO- http://localhost:7474 answers | every 10 s, 30 attempts, 30 s grace period |
As long as the test fails, the container is starting; when it succeeds, healthy; after 30 failures, unhealthy. The kibana service declares depends_on: elasticsearch: condition: service_healthy: Compose only launches it once Elasticsearch is healthy, which avoids the infamous "Kibana server is not ready yet" at startup.
Two details of the compose file deserve a word right now. xpack.security.enabled=false: no password or certificate for Elasticsearch; this is a local lab, never a production configuration. cluster.routing.allocation.disk.threshold_enabled=false: Elasticsearch will not switch your indexes to read-only if your disk goes over 95% (lesson 04, failure no. 5).
The lab kit first. Everything that follows is run from the root of the kit — https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fr — retrieved with
git clone https://github.com/hrhouma2/aiopsatlas-recherche-graphes-labo-fror with the Code → Download ZIP button (lesson 02, step 4). Ifdir(orls) does not showdocker-compose.yml,labo.sh, andlabo.ps1, you are not in the right place.
All the queries of this lesson: elasticsearch/requetes/01-03-premiere-visite.txt.
Start the stack. From the kit folder:
./labo.sh demarrer.\labo.ps1 demarrerThe script displays three blocks: == Téléchargement des images == (long the first time, silent afterward), == Démarrage == (Compose's Container labo-elasticsearch Started lines), then == Attente que chaque service soit prêt == where each line fills with dots until prêt (…s). It ends with:
Le labo est prêt.
Kibana http://localhost:5601 (Dev Tools : menu ☰ → Management → Dev Tools)
Elasticsearch http://localhost:9200
Neo4j Browser http://localhost:7474 (utilisateur neo4j · mot de passe aiopsatlas2026)
Étape suivante : ./labo.sh importer puis ./labo.sh charger-grapheWhat you should see: three prêt (elasticsearch, kibana, neo4j). Allow one to two minutes after the download. If a line shows unhealthy, exited, or délai dépassé, go straight to lesson 04.
Load the Elasticsearch indexes.
./labo.sh importerThe command creates each index with its mapping (files elasticsearch/mappings/*.json) then sends the data to the _bulk API. Real output, here on a lab where the indexes already existed (the command can be replayed safely):
== Import dans elasticsearch ==
— index cours existe déjà — conservé
✔ données cours chargées
— index avis existe déjà — conservé
✔ données avis chargées
— index acces existe déjà — conservé
✔ données acces chargées
index docs.count store.size
acces 12000 1.5mb
avis 609 74.6kb
cours 504 183.6kb
Import terminé. Attendu : cours = 504, avis = 609, acces = 12000.What you should see: the first time, the — lines become ✔ index cours créé avec son mapping. The three counters must be exactly 504, 609, and 12000; the documents carry their own _id, so rerunning the import never creates a duplicate.
Load the Neo4j graph.
./labo.sh charger-grapheThe script executes neo4j/cypher/01-contraintes.cypher (uniqueness constraints and indexes) then 02-charger.cypher (LOAD CSV + MERGE for each file in neo4j/import/). It ends with the summary computed by the script's last query, MATCH (n) RETURN labels(n)[0] AS etiquette, count(*) AS noeuds ORDER BY etiquette:
etiquette, noeuds
"Competence", 22
"Cours", 504
"Etudiant", 300
"Professeur", 30
"Ville", 16
Graphe chargé. Attendu : Competence 22, Cours 504, Etudiant 300, Professeur 30, Ville 16.What you should see: 872 nodes in total. Everything uses MERGE, so replaying the command creates no duplicate.
Read the lab status. This is the command you will type most often:
./labo.sh etatReal output (here with the OpenSearch profile active, which will not appear on your machine before module 5):
== Conteneurs ==
NAME STATUS PORTS
labo-elasticsearch Up 16 minutes (healthy) 0.0.0.0:9200->9200/tcp, [::]:9200->9200/tcp
labo-kibana Up 16 minutes (healthy) 0.0.0.0:5601->5601/tcp, [::]:5601->5601/tcp
labo-neo4j Up 14 minutes (healthy) 0.0.0.0:7474->7474/tcp, [::]:7474->7474/tcp, 0.0.0.0:7687->7687/tcp, [::]:7687->7687/tcp
labo-opensearch Up 16 minutes (healthy) 0.0.0.0:9201->9200/tcp, [::]:9201->9200/tcp
labo-opensearch-dashboards Up 16 minutes 0.0.0.0:5602->5601/tcp, [::]:5602->5601/tcp
== Services ==
✔ Elasticsearch : {"status":"green","number_of_nodes":1}
index : acces 12000 avis 609 cours 504
✔ Kibana répond (http://localhost:5601)
✔ Neo4j répond — nœuds : 872
✔ OpenSearch : {"status":"green","number_of_nodes":1}What you should see: (healthy) on every container line, green for the cluster, the three index counters, and 872 nodes. Without OpenSearch, the last line says — OpenSearch non démarré (profil optionnel : ./labo.sh demarrer opensearch).
Open Kibana Dev Tools. Go to http://localhost:5601. Kibana is in French (I18N_LOCALE=fr-FR). Click the ☰ menu at the top left; in the Management section, choose Outils de développement (the French translation of "Dev Tools"; the breadcrumb shows Outils de développement > Console). You land on the Console tab, Shell sub-tab: an editor on the left, the response on the right. The Effacer cette entrée (Clear this input) button empties the sample editor.
What you should see: when you type a query, a ▶ button (« Cliquer pour envoyer la requête », Click to send request) appears at the end of the line. Shortcut: Ctrl + Enter (⌘ + Enter on Mac) sends the query under the cursor. At the bottom right of the response, Kibana displays the HTTP code and the duration, for example 200 - OK 23 ms.
First query: cluster health.
GET _cluster/health{
"cluster_name": "labo",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 53,
"active_shards": 53,
"unassigned_shards": 0,
…
"active_shards_percent_as_number": 100
}What you should see: "status": "green". The lab indexes are created with number_of_replicas: 0, so no replica is waiting: green on a single node. The number of shards (53) includes Kibana system indexes; it may vary.
Who answers, and which indexes exist?
GET /Response: "name": "labo-es-1", "cluster_name": "labo", "version": { "number": "9.5.3", …, "lucene_version": "10.5.1" }, "tagline": "You Know, for Search".
GET _cat/indices/cours,avis,acces?v&s=indexhealth status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
green open acces aii68fsfQXKyt5wqkE1mPA 1 0 12000 0 1.5mb 1.5mb 1.5mb
green open avis W9j_JrwJT4mdzpWcS5k7xg 1 0 609 0 74.6kb 74.6kb 74.6kb
green open cours pmq403ZgSZWeHJY9uNw1Qw 1 0 504 0 183.6kb 183.6kb 183.6kbWhat you should see: pri 1, rep 0, and the three counters. ?v adds the header line, s=index sorts by name. Without the cours,avis,acces filter, GET _cat/indices?v also lists internal indexes whose names start with a dot (.internal.alerts-…): ignore them.
The node and the counts.
GET _cat/nodes?vip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
172.24.0.2 39 63 2 0.43 0.38 0.64 cdfhilmrstw * labo-es-1GET cours/_count{ "count": 504, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 } }Same for GET avis/_count → "count": 609 and GET acces/_count → "count": 12000. What you should see: a single node, master *, and heap.percent around 40% with the compose file's -Xmx1g setting.
A first document.
GET cours/_search{
"query": { "match_all": {} },
"size": 1
}Response (truncated):
{
"took": 1,
"hits": {
"total": { "value": 504, "relation": "eq" },
"max_score": 1,
"hits": [
{
"_index": "cours",
"_id": "C0001",
"_score": 1,
"_source": {
"id": "C0001",
"titre": "Docker expliqué simplement",
"categorie": "DevOps", "sujet": "Docker", "niveau": "debutant",
"prix": 129, "note_moyenne": 4.4, "nb_avis": 327,
"professeur": { "id": "P001", "nom": "Karim Caron", "ville": "Gatineau" },
…
}
}
]
}
}What you should see: hits.total.value = 504 (all documents match match_all) but only one is returned thanks to size: 1. Dev Tools sends a GET with a body as a POST under the hood; both are accepted.
Open Neo4j Browser and connect. Go to http://localhost:7474. The Connect to instance window already offers Protocol neo4j:// and Connection URL localhost:7687 (the Bolt port, not 7474). Leave Database user as neo4j, type the Password aiopsatlas2026, click Connect. What you should see: at the top, a green dot and Instance: neo4j://localhost:7687, Database: neo4j, User: neo4j. The Database information panel on the left shows Nodes (872) with the labels Competence, Cours, Etudiant, Professeur, Ville, and Relationships (3,712) with COUVRE, ENSEIGNE, HABITE, INSCRIT_A, PREREQUIS_DE. The sidebar offers Database overview, Saved Cypher, History, Cypher reference, Parameters, Settings (the interface is in English).
First Cypher query. In the editor at the top (neo4j$), type then click Run (or Ctrl + Enter):
MATCH (c:Cours) RETURN c LIMIT 25What you should see: a result frame with three views Graph, Table, Raw, and on the right Results overview: Nodes (25) · Cours (25). In Graph view, 25 orange bubbles; click one of them to see its properties (id, titre, categorie, prix…). In Table view, the same 25 nodes as JSON. Module 6 teaches you to write these queries; for now, you know where to type them.
And OpenSearch? The lab can also start OpenSearch 3.8.0 and OpenSearch Dashboards on ports 9201 and 5602, with ./labo.sh demarrer opensearch then ./labo.sh importer opensearch. It is an optional profile of Compose: it is not launched by default, and it requires 6 GB of memory for Docker. We activate it in module 5, to replay the same queries on both engines and compare. Nothing to do today.
Kibana shows "Kibana server is not ready yet" right after demarrer → Kibana has started but has not yet finished connecting to Elasticsearch and creating its internal indexes. Wait 30 seconds and reload the page. If the message persists beyond two minutes, ./labo.sh journal kibana (lesson 04, failure no. 4).
Dev Tools answers 404 with "type": "index_not_found_exception", "reason": "no such index [cour]" → Typo in the index name (cour instead of cours). Elasticsearch does not guess; GET _cat/indices?v gives you the exact names. Variant: 400 with "no handler found for uri [/cours/_serch] and method [GET]" → this time the API name is misspelled (_serch).
Dev Tools answers 400 with x_content_parse_exception … was expecting double-quote to start field name → The JSON is malformed, most often an extra comma before the closing brace ("size": 1, }). The editor underlines the faulty line in red even before sending.
Neo4j Browser: "Connection to instance failed — The client is unauthorized due to authentication failure." (details: Neo.ClientError.Security.Unauthorized) → Wrong password. It is aiopsatlas2026 (set by NEO4J_AUTH in the compose file), not the default password neo4j. Retype it, with no trailing space.
etat shows Neo4j répond — nœuds : 0 → The container is running but you forgot charger-graphe. Run it; it takes about thirty seconds.
demarrer, importer, charger-graphe. The last two can be replayed without creating duplicates (explicit identifiers on the Elasticsearch side, MERGE on the Neo4j side).demarrer waits for each container's healthy status; that status comes from the healthchecks in docker-compose.yml, and Kibana only starts after Elasticsearch thanks to depends_on … service_healthy.etat at a glance: (healthy) everywhere, cluster green, 504 / 609 / 12000 documents, 872 nodes.GET path line followed by its JSON pasted underneath; Ctrl + Enter to send.localhost:7687, user neo4j, password aiopsatlas2026; results are read in Graph, Table, or Raw.The file 01-03-premiere-visite.txt is in the exact Dev Tools format: in the console, the Importer les requêtes (Import requests) button (icon at the top right of the editor) accepts this file and pastes it into the editor. All the files in the elasticsearch/requetes/ folder work this way; you will never have to retype a query from the course.