Class: Pikuri::VectorDb::Server::DockerContainer
- Inherits:
-
Object
- Object
- Pikuri::VectorDb::Server::DockerContainer
- Defined in:
- lib/pikuri/vector_db/server/docker_container.rb
Overview
Lifecycle engine for one named, disposable docker container with persistent bind-mounted data — the shared machinery Qdrant and Chroma compose. The supervisor carries the engine’s identity (image pin, container name, persist path, heartbeat path) and hands it all over here as explicit constructor parameters; this class does the docker work: create / recreate / remove the container, publish the port, heartbeat-poll until ready.
Namespace squat: pikuri-internal-*
Containers are named “pikuri-internal-<engine>” and carry the pikuri.internal=true docker label. Any container under such a name is treated as fully owned by pikuri — wrong image, stopped, or left behind by a crash, it is removed and recreated on the pinned image without ceremony. The data volume is bind-mounted from the user’s cache directory and is not nuked by this — the user’s corpus is theirs, even when the container that runs against it gets replaced. The same convention scales to future internal containers (rerankers, alternative vector stores): anything starting with “pikuri-internal-” is fair game for pikuri to manage.
Ephemeral container, persistent data
The container is disposable; the data is not. Every byte of engine state lives on the host volume bind-mounted into the container, so the container itself carries nothing worth keeping. #ensure_running! therefore recreates it from scratch on every boot — the lone exception is a container this same instance already started and left running, which it reuses (so repeated calls within one process stay cheap). A stopped container, or one left running by a previous run / a crash / a concurrent pikuri, is removed and recreated rather than adopted: a fresh container on the pinned image against the persistent volume is always a clean, known-good start, and the image tag never has to be reconciled. #close docker rm -fs the container — nothing is left behind, not even a stopped shell. The single-named-container convention assumes one active pikuri at a time; a second concurrent pikuri would remove the first’s container out from under it.
Subprocess seam
Docker invocations (+docker inspect+, docker run, docker rm -f) are short-lived shell-outs — capture output, check exit, act. They route through Subprocess.spawn like the rest of pikuri-* lib/; this class is not an exception to the subprocess seam, unlike pikuri-mcp‘s ClientWrapper (which owns a long-lived stdio pipe the mcp gem mediates).
Bind 127.0.0.1, not 0.0.0.0
The port publish is built here as -p 127.0.0.1:<host>:<container>, never the docker default -p <host>:<container>. The default binds the host port to every interface, which would expose the user’s indexed corpus to anyone on the same LAN. Centralizing the publish string in this class makes the privacy posture a single enforcement point rather than a per-supervisor convention.
Errors are loud at boot, quiet at teardown
Docker missing, docker run exit non-zero, healthcheck timeout — all raise RuntimeError with the offending output, prefixed with the container name. Caller is internal pikuri code (a host’s Agent.new block running at boot); this is bug territory, not “tell the model and let it retry”. #close is the opposite: best-effort, idempotent, logs instead of raising — teardown shouldn’t abort on a container that’s already gone.
Constant Summary collapse
- LOGGER =
Pikuri.logger_for('VectorDb::Server')
Instance Attribute Summary collapse
-
#name ⇒ String
readonly
Container name.
Instance Method Summary collapse
-
#close ⇒ void
Remove the container (+docker rm -f+), leaving the bind-mounted host volume — the data survives.
-
#endpoint ⇒ String
“localhost:<host_port>”.
-
#ensure_running! ⇒ void
Idempotent: ensure a fresh container is running, then heartbeat-poll until ready.
- #initialize(name:, image:, label:, host_port:, container_port:, volume:, health_path:, healthcheck_timeout:, connection: nil) ⇒ DockerContainer constructor
Constructor Details
#initialize(name:, image:, label:, host_port:, container_port:, volume:, health_path:, healthcheck_timeout:, connection: nil) ⇒ DockerContainer
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 106 def initialize(name:, image:, label:, host_port:, container_port:, volume:, health_path:, healthcheck_timeout:, connection: nil) @name = name @image = image @label = label @host_port = host_port @container_port = container_port @volume = volume @health_path = health_path @healthcheck_timeout = healthcheck_timeout @connection = connection @owns_container = false @closed = false end |
Instance Attribute Details
#name ⇒ String (readonly)
Returns container name.
123 124 125 |
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 123 def name @name end |
Instance Method Details
#close ⇒ void
This method returns an undefined value.
Remove the container (+docker rm -f+), leaving the bind-mounted host volume — the data survives. Best-effort and idempotent: a no-op if this instance never started a container or has already closed, and a non-zero rm is logged rather than raised — that’s teardown, boot is loud.
163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 163 def close return if @closed || !@owns_container @closed = true result = docker('rm', '-f', @name) return if result.status.success? LOGGER.warn( "docker rm -f #{@name} failed " \ "(exit #{result.status.exitstatus}): #{result.output.strip}" ) end |
#endpoint ⇒ String
Returns “localhost:<host_port>”.
126 127 128 |
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 126 def endpoint "http://localhost:#{@host_port}" end |
#ensure_running! ⇒ void
This method returns an undefined value.
Idempotent: ensure a fresh container is running, then heartbeat-poll until ready. A container this instance already started and left running is reused (a second call is one docker inspect plus a heartbeat round trip); anything else — a stopped container, a running one this instance did not start, or no container at all — is removed (if present) and recreated on the pinned image. See “Ephemeral container, persistent data” in the class header.
142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 142 def ensure_running! state = container_state if @owns_container && state == :running LOGGER.info("#{@name} already running") else remove_container! unless state == :missing run_container! end @owns_container = true @closed = false wait_for_healthy! end |