+1 (415) 649-9454

Getting Started with Neo4j 2026.x in Docker

Every GraphGuru engagement starts the same way: a throwaway Neo4j instance on a laptop so we can model, load a sample, and argue about Cypher before anyone touches a real server. Since Neo4j moved to calendar versioning in January 2025, the fastest way to get a current instance — one that speaks Cypher 25 and matches what you will run in production — is the official Docker image. This tutorial gets you from nothing to a queryable Neo4j 2026.x database with APOC installed in about ten minutes.

What you need

  • Docker Desktop (or Docker Engine + Compose v2) on macOS, Windows, or Linux
  • Roughly 2 GB of free RAM for the container
  • A terminal

We will use the 2026.05 image throughout. Neo4j ships a new CalVer release every month (YYYY.MM.patch), so substitute the newest tag from Docker Hub if you are reading this later; everything below works on any 2025.06+ release.

Step 1 — the Compose file

Create a working directory and save this as compose.yaml:

services:
  neo4j:
    image: neo4j:2026.05
    container_name: neo4j-dev
    ports:
      - "7474:7474"   # HTTP / Neo4j Browser
      - "7687:7687"   # Bolt / drivers
    environment:
      NEO4J_AUTH: neo4j/change-me-please   # min 8 characters
      NEO4J_PLUGINS: '["apoc"]'
      NEO4J_server_memory_heap_initial__size: 1G
      NEO4J_server_memory_heap_max__size: 1G
      NEO4J_server_memory_pagecache_size: 512M
    volumes:
      - neo4j-data:/data
      - neo4j-logs:/logs
      - ./import:/var/lib/neo4j/import
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:7474"]
      interval: 10s
      timeout: 5s
      retries: 12

volumes:
  neo4j-data:
  neo4j-logs:

Three things worth understanding in that file:

  1. NEO4J_AUTH sets the initial password for the neo4j user. Neo4j refuses passwords shorter than eight characters, and the value is only read on the first start of an empty /data volume. Change it later with ALTER CURRENT USER SET PASSWORD.
  2. NEO4J_PLUGINS: '["apoc"]' tells the image's entrypoint to download the matching APOC Core release and enable it. Note the distinction: APOC Core ships with Neo4j and is what you get here; APOC Extended is a separate community project that you would have to mount into /plugins yourself. Most of the APOC functions people reach for out of habit (apoc.create.uuid, apoc.date.* helpers, apoc.text.join) now have native Cypher equivalents (randomUUID(), temporal functions, reduce/string functions), so check the Cypher manual before you depend on a procedure.
  3. Config via environment uses the NEO4J_ prefix with _ for . and __ for _, so server.memory.heap.max_size becomes NEO4J_server_memory_heap_max__size. Every setting in the operations manual can be set this way.

This is the Community edition. For Enterprise features (multi-database, Autonomous Clustering, RBAC) use neo4j:2026.05-enterprise and add NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes"; a free developer licence is fine for local use.

Step 2 — start it

mkdir -p import
docker compose up -d
docker compose logs -f neo4j

Wait for the line Started. and then open http://localhost:7474. Log in with neo4j / change-me-please. You are in Neo4j Browser, connected over Bolt on 7687.

If you prefer the terminal, the image bundles cypher-shell:

docker exec -it neo4j-dev cypher-shell -u neo4j -p change-me-please

Step 3 — confirm the version and APOC

CALL dbms.components() YIELD name, versions, edition
RETURN name, versions, edition;

You should see Neo4j Kernel, ["2026.05.0"], community. Then:

SHOW FUNCTIONS YIELD name
WHERE name STARTS WITH 'apoc.'
RETURN count(*) AS apocFunctions;

A non-zero count means the plugin loaded. If it is zero, check docker compose logs neo4j for a download failure — corporate proxies are the usual culprit.

Step 4 — your first Cypher 25 queries

Since 2025.06, every Neo4j release carries two Cypher language versions side by side: Cypher 5 (the default, for compatibility) and Cypher 25, the GQL-aligned evolution that new features land in. You pick per query with a prefix, or per database with a default.

Create a tiny graph first:

CREATE (a:Person {name: 'Ada', born: 1815})
CREATE (g:Person {name: 'Grace', born: 1906})
CREATE (l:Person {name: 'Linus', born: 1969})
CREATE (a)-[:MENTORED {year: 1843}]->(g)
CREATE (g)-[:MENTORED {year: 1991}]->(l);

Now a Cypher 25 query that uses two additions you will not find in Cypher 5 — LET to bind expressions and FILTER as a standalone clause:

CYPHER 25
MATCH (p:Person)
LET age = 2026 - p.born
FILTER age > 60
RETURN p.name AS name, age
ORDER BY age DESC;

And a quantified path pattern for "chains of mentoring, 1 to 3 hops", which is the modern replacement for variable-length *1..3 syntax:

CYPHER 25
MATCH (start:Person {name: 'Ada'}) (()-[:MENTORED]->()){1,3} (end:Person)
RETURN end.name;

To make Cypher 25 the default so you can drop the prefix:

ALTER DATABASE neo4j SET DEFAULT LANGUAGE CYPHER 25;

Drivers inherit that default, which is exactly what you want when a team is standardising on the new dialect. Keep Cypher 5 as the default on any database that still serves legacy application code until you have run its queries through the deprecations list.

Step 5 — connect from Python

The neo4j driver 6.x is the current major line and the one that understands the native Vector type introduced in 2025.10.

pip install "neo4j>=6"
from neo4j import GraphDatabase

URI = "bolt://localhost:7687"
AUTH = ("neo4j", "change-me-please")

with GraphDatabase.driver(URI, auth=AUTH) as driver:
    driver.verify_connectivity()
    records, summary, keys = driver.execute_query(
        """
        CYPHER 25
        MATCH (p:Person)
        LET age = 2026 - p.born
        RETURN p.name AS name, age
        ORDER BY age DESC
        """,
        database_="neo4j",
    )
    for r in records:
        print(r["name"], r["age"])
    print(f"{summary.result_available_after} ms")

execute_query is the right entry point for almost everything: it handles sessions, retries, and routing for you. Reach for explicit session.execute_write / execute_read only when you need multi-statement transactions.

Step 6 — load a CSV from the import directory

The Compose file mounted ./import into the container, so any file you drop there is reachable as file:///name.csv:

cat > import/people.csv <<'CSV'
name,born
Margaret,1936
Barbara,1949
CSV
LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
MERGE (p:Person {name: row.name})
SET p.born = toInteger(row.born);

For anything larger than a few million rows you will want neo4j-admin database import or the Data Importer — we cover both in Importing Relational Data Without the (Dead) ETL Tool.

Resetting, upgrading, and cleaning up

  • Wipe everything: docker compose down -v removes the container and the named volumes.
  • Move to a newer monthly release: change the image tag and docker compose up -d. Within the CalVer line, stores upgrade automatically on start; Neo4j's upgrade guide lists the few cases that need neo4j-admin database migrate.
  • Keep it out of your RAM budget: docker compose stop when you are done for the day.

Where this goes next

You now have the same stack our consultants use on day one of a project. From here, the natural next steps are modelling your own domain, loading real data, and — for most 2026 projects — adding a vector index so the graph can serve retrieval for an LLM application. Our GraphRAG pipeline tutorial picks up from exactly this container.

If you would rather have a senior Neo4j engineer set this up inside your environment — with the right memory settings, clustering topology, and security model for production — talk to us.