7. Volumes and a real service
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
If you develop in C# or Java, you have probably started a database for local work with a single docker run: PostgreSQL for an Entity Framework Core or JPA project, Redis for a cache, a vector database for a retrieval experiment. Three questions always come back. Which port do I publish? Which version did I just download? Where do the data live when I delete the container?
This lesson answers them with wslc and a real service, qdrant, a vector search engine with a REST API on port 6333 and a gRPC API on port 6334. The machine already runs a qdrant in Docker Desktop, used by another application, which makes the exercise realistic: the wslc copy must not disturb it. Every output below is from wslc 2.9.11, recorded in the journal.
Choosing the Windows ports
Section titled “Choosing the Windows ports”Before starting anything, look at what is already published:
> docker psga-qdrant qdrant/qdrant:latest Up About an hour (healthy) 0.0.0.0:6333-6334->6333-6334/tcp, [::]:6333-6334->6333-6334/tcpDocker Desktop publishes 6333 and 6334 on every IPv4 and IPv6 address. As lesson 5 shows, wslc would publish the same ports without any error, on 127.0.0.1, and silently take that address away from the application that uses ga-qdrant. So the wslc copy uses 16333 and 16334 on the Windows side. Inside its container, qdrant still listens on 6333 and 6334: only the left side of -p changes.
Pulling: same tag, different version
Section titled “Pulling: same tag, different version”wslc pull qdrant/qdrant # 21 s, 198 MBThe Docker image was already on the disk, but wslc downloads it again, because the two tools keep separate image stores. The surprise comes later, when both copies answer on their root URL:
> curl.exe http://127.0.0.1:16333/ # wslc{"title":"qdrant - vector search engine","version":"1.19.1","commit":"6ab21cac18ebb6f4ae29102c7f8f5cc11affd5de"}> curl.exe http://127.0.0.1:6333/ # Docker (ga-qdrant, pulled on 2025-12-19){"title":"qdrant - vector search engine","version":"1.16.3","commit":"bd49f45a8a2d4e4774cac50fa29507c4e8375af2"}Both images are called qdrant/qdrant:latest, and they are three minor versions apart. A tag is a movable label, a bit like a floating NuGet version or a Maven LATEST: latest means whatever was latest when this tool pulled it. When two environments must match, pin the version, as in qdrant/qdrant:v1.19.1.
A named volume
Section titled “A named volume”A container’s own filesystem disappears with the container. For a database, the data must live somewhere else: a named volume, created by the container engine and mounted at the path where the service writes.
wslc volume create qdrant-datawslc run -d --name qdrant -p 16333:6333 -p 16334:6334 -v qdrant-data:/qdrant/storage qdrant/qdrantcurl.exe http://127.0.0.1:16333/readyz # all shards are ready (after 1 s)CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESda52872e3246 qdrant/qdrant "./entrypoint.sh" 5 seconds ago Up 1 second 127.0.0.1:16333->6333/tcp, 127.0.0.1:16334->6334/tcp qdrant| Option | Effect |
|---|---|
-p 16333:6333 |
REST API: Windows port 16333 to container port 6333 |
-p 16334:6334 |
gRPC API: Windows port 16334 to container port 6334 |
-v qdrant-data:/qdrant/storage |
mounts the named volume where qdrant keeps its collections |
The readyz endpoint is qdrant’s readiness probe: it answers once the service can take requests, which is what a script should wait for rather than a fixed delay.
The diagram shows the two paths into the container: the published ports from Windows, and the volume that lives in the session’s virtual disk.
flowchart LR subgraph windows["Windows"] client["curl.exe or your application"] vhdx["storage.vhdx of the session"] end subgraph vm["wslc session VM"] subgraph ctr["container qdrant"] rest["REST on 6333"] grpc["gRPC on 6334"] data["/qdrant/storage"] end vol["volume qdrant-data"] end client -->|"127.0.0.1:16333"| rest client -->|"127.0.0.1:16334"| grpc data --> vol vol -.->|"stored in"| vhdxPutting data in
Section titled “Putting data in”A collection of four-dimensional vectors, three points with a small payload, and a query. The request bodies are in JSON files, which avoids PowerShell’s quoting rules for double quotes passed to a native program:
curl.exe -X PUT http://127.0.0.1:16333/collections/journal -H "Content-Type: application/json" --data-binary "@coll.json"curl.exe -X PUT "http://127.0.0.1:16333/collections/journal/points?wait=true" -H "Content-Type: application/json" --data-binary "@points.json"curl.exe -X POST http://127.0.0.1:16333/collections/journal/points/query -H "Content-Type: application/json" --data-binary "@query.json"coll.json {"vectors":{"size":4,"distance":"Cosine"}}points.json {"points":[{"id":1,"vector":[0.9,0.1,0.1,0.1],"payload":{"note":"wslc sessions"}},{"id":2,"vector":[0.1,0.9,0.1,0.1],"payload":{"note":"ports and localhost"}},{"id":3,"vector":[0.1,0.1,0.9,0.1],"payload":{"note":"storage.vhdx"}}]}query.json {"query":[0.2,0.8,0.1,0.1],"limit":2,"with_payload":true}{"result":true,"status":"ok","time":0.24333272}{"result":{"operation_id":1,"status":"completed"},"status":"ok","time":0.003567116}{"result":{"points":[{"id":2,"version":1,"score":0.99111706,"payload":{"note":"ports and localhost"}},{"id":1,"version":1,"score":0.3651484,"payload":{"note":"wslc sessions"}}]},"status":"ok","time":0.00189502}The query vector points mostly along the second axis, so point 2 comes first with a cosine similarity of about 0.99, and point 1 follows far behind. wait=true makes the upsert return only once the points are indexed, so the query that follows sees them.
Deleting the container, keeping the data
Section titled “Deleting the container, keeping the data”wslc container stop qdrantwslc container remove qdrantwslc run -d --name qdrant -p 16333:6333 -p 16334:6334 -v qdrant-data:/qdrant/storage qdrant/qdrantcurl.exe http://127.0.0.1:16333/collectionscurl.exe -X POST http://127.0.0.1:16333/collections/journal/points/count -H "Content-Type: application/json" -d "{}"{"result":{"collections":[{"name":"journal"}]},"status":"ok","time":8.866e-6}{"result":{"count":3},"status":"ok","time":0.007211133}The new container finds the collection and its three points, because they live in the volume, not in the container. wslc volume inspect qdrant-data shows where: "Driver": "guest" and a mount point inside the session VM, /var/lib/docker/volumes/qdrant-data/_data. In other words, the data sit in the session’s storage.vhdx on the Windows side, which has two consequences:
- the volume belongs to one session: an administrator terminal, which uses another session, doesn’t see it (lesson 3);
- deleting data from the volume frees space inside the virtual disk, but the file doesn’t shrink on its own (lesson 6).
Don’t mount a Windows folder for a database
Section titled “Don’t mount a Windows folder for a database”With Docker you may be used to a bind mount, where -v takes a host folder instead of a volume name, so that the data files are visible in Explorer. wslc accepts a Windows path:
wslc run -d --name qdrant-bind -p 16335:6333 -v "C:\...\qdrant-bind:/qdrant/storage" qdrant/qdrantwslc exec qdrant-bind sh -c "mount | grep /qdrant/storage"drvfs on /qdrant/storage type virtiofs (rw,relatime)The folder reaches the Linux side through virtiofs, a shared filesystem between the VM and Windows. qdrant still starts and writes its files into the Windows folder, but it checks the filesystem at startup and logs an error:
ERROR qdrant: Filesystem check failed for storage path ./storage. Details: FUSE filesystems may cause data corruption due to caching issuesThe same rule applies to PostgreSQL, SQL Server or any engine that relies on precise file locking and flushing: keep its files in a volume, inside the VM, and use a bind mount for source code, configuration files or exports.
What it costs
Section titled “What it costs”> wslc stats qdrantCONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDSda52872e3246 qdrant 0.16% 47.21MiB / 15.62GiB 0.30% 10.1kB / 5.77kB 8.19kB / 184kB 35> docker stats ga-qdrant --no-stream --format "CPU {{.CPUPerc}} MEM {{.MemUsage}}"CPU 0.41% MEM 367.8MiB / 31.2GiB- The limit in each line is the VM’s, not the container’s: 15.62 GiB for
wslc, because this machine setsmemorySize: 16GB, and 31.2 GiB for Docker Desktop, half of the RAM by default. - 47 MiB for three points against 368 MiB for
ga-qdrantis not a comparison between the tools:ga-qdrantholds real data. - On the Windows side, the
vmmemwslc-cli-spareprocess of the session VM was at 1160 MB, of which about 0.9 GB is the cost of an idle session. - qdrant logs
starting 7 workers: it sees the 8 CPUs allowed bycpuCount: 8.
Lesson 6 explains those limits and how to measure the VM.
Cleaning up
Section titled “Cleaning up”wslc container stop qdrant qdrant-bindwslc container remove qdrant qdrant-bindwslc volume remove qdrant-dataRemoving the container keeps the volume; removing the volume deletes the collections for good. ga-qdrant, in Docker, was never touched.
Key takeaways
Section titled “Key takeaways”- When another tool already publishes a port, pick another Windows port: only the host side of
-pchanges, and nothing warns you about a conflict. latestis not a version. The same tag pulled at two dates gave qdrant 1.16.3 in Docker and 1.19.1 inwslc; pin the tag when environments must match.- A named volume survives the container. It lives in the session’s
storage.vhdxand is only visible from that session. - A service logs its container-side ports; from Windows, use the published port on
127.0.0.1. - For a database, use a volume, not a Windows folder: a bind mount goes through virtiofs, and qdrant warns about data corruption.
wslc statsshows the VM’s memory as the limit, not a per-container limit.
Exercises
Section titled “Exercises”- Without running it, predict which point comes first for the query vector
[0.1,0.1,0.9,0.1], and roughly its score. Then run the query.
Solution
Point 3, storage.vhdx, whose vector is exactly the query vector. The cosine similarity of a vector with itself is 1, so the score should be 1 or a floating-point value very close to it. Points 1 and 2 are symmetric with respect to this query and should get the same, much lower score. The body of query.json becomes:
{"query":[0.1,0.1,0.9,0.1],"limit":2,"with_payload":true}- Docker Desktop publishes qdrant on
0.0.0.0:6333and[::]:6333, and nothing else uses that port. Which qdrant dohttp://127.0.0.1:6333/andhttp://localhost:6333/reach? What changes if you now runwslc run -d -p 6333:6333 qdrant/qdrant?
Solution
Before the wslc container, both addresses reach Docker, which listens on every address. After it, wslc listens on 127.0.0.1:6333, the more specific address: 127.0.0.1 reaches wslc, while localhost, which resolves to ::1 first, still reaches Docker. Neither command fails. The version in the root response tells you which one answered.
- A colleague starts qdrant with
wslc run -d --name qdrant -p 16333:6333 qdrant/qdrant, creates a collection, then stops and removes the container and starts it again with the same command. Is the collection still there? Why?
Solution
No. Without -v, qdrant writes into the container’s own filesystem, which is deleted with the container. The fix is the named volume of this lesson: -v qdrant-data:/qdrant/storage. To verify: whether the qdrant image declares an anonymous volume that would keep an orphaned copy of the data in the session.
- You want the two qdrant copies to run the same version. Write the
wslc runcommand, and say what you would change on the Docker side.
Solution
wslc run -d --name qdrant -p 16333:6333 -p 16334:6334 -v qdrant-data:/qdrant/storage qdrant/qdrant:v1.19.1On the Docker side, use the same tag, qdrant/qdrant:v1.19.1, instead of latest. Before upgrading a qdrant that holds real data, read the qdrant release notes: the storage format may change between versions.
- From an administrator terminal,
wslc volume listdoesn’t showqdrant-data. Has the volume been lost?
Solution
No. The administrator terminal uses the wslc-cli-admin-<user> session, whose engine and disk are separate. The volume is in the non-elevated session. From the administrator terminal, you can still see it with wslc --session wslc-cli-<user> volume list; the reverse, from a normal terminal to the admin session, requires elevation.
Sources
Section titled “Sources”- qdrant documentation: installation and API reference
- qdrant/qdrant — Docker Hub
- WSL container — Microsoft Learn
- Volumes and bind mounts — Docker docs (
wslcfollows the same model) - virtiofs