Class: Pikuri::VectorDb::Server::DockerContainer

Inherits:
Object
  • Object
show all
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, name, persist/heartbeat paths) as constructor parameters; this class does the docker work: create/recreate/remove, publish the port, heartbeat-poll until ready.

Namespace squat: pikuri-internal-*

Containers are named "pikuri-internal-<engine>" with a pikuri.internal=true label, and are treated as fully pikuri-owned — wrong image, stopped, or crash-orphaned, they are removed and recreated on the pinned image without ceremony. The bind-mounted data volume is not nuked — the corpus is the user's even when its container is replaced. The convention scales to any future pikuri-internal-*.

Ephemeral container, persistent data

All engine state lives on the host volume, so the container carries nothing worth keeping: #ensure_running! recreates it every boot, except one this instance already started and left running (reused, so repeated calls stay cheap). Anything else — stopped, or running from a previous run / crash / concurrent pikuri — is removed and recreated rather than adopted: a fresh container on the pinned image against the persistent volume is always a clean start with no image tag to reconcile. #close +docker rm -f+s it. The single-named-container convention assumes one active pikuri at a time.

Docker shell-outs route through Subprocess.spawn per the subprocess seam — this class is not the seam's exception (that's +pikuri-mcp+'s ClientWrapper, which owns a long-lived stdio pipe). The port publish is built -p 127.0.0.1:<host>:<container>, never the docker default (which binds every interface and would expose the corpus to the LAN) — centralized here as the single privacy enforcement point. Errors are loud at boot (docker missing, run non-zero, healthcheck timeout → RuntimeError — internal caller, bug territory) and quiet at teardown (#close best-effort, logs instead of raising).

Constant Summary collapse

LOGGER =
Pikuri.logger_for('VectorDb::Server')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, image:, label:, host_port:, container_port:, volume:, health_path:, healthcheck_timeout:, connection: nil) ⇒ DockerContainer

Parameters:

  • name (String)

    container name, e.g. "pikuri-internal-qdrant" — see "Namespace squat" in the class header.

  • image (String)

    pinned docker image, e.g. "qdrant/qdrant:v1.12.4".

  • label (String)

    docker label set on the container, e.g. "pikuri.internal=true".

  • host_port (Integer)

    host port to publish, bound to 127.0.0.1 only.

  • container_port (Integer)

    the engine's port inside the container, e.g. 6333 for qdrant.

  • volume (String)

    full -v bind-mount argument, "<host dir>:<container persist dir>". The host side must exist before #ensure_running! (the supervisor +mkdir_p+s it — docker would otherwise create it root-owned).

  • health_path (String)

    HTTP path heartbeat-polled until it returns 200, e.g. "/healthz".

  • healthcheck_timeout (Integer)

    seconds to poll health_path before giving up.

  • connection (Faraday::Connection, nil) (defaults to: nil)

    DI hook for tests. Production callers leave it nil.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 71

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

#nameString (readonly)

Returns container name.

Returns:

  • (String)

    container name.



88
89
90
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 88

def name
  @name
end

Instance Method Details

#closevoid

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.



124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 124

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

#endpointString

Returns "http://localhost:<host_port>".

Returns:

  • (String)

    "http://localhost:<host_port>".



91
92
93
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 91

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. One this instance already started is reused; anything else is removed (if present) and recreated on the pinned image. See "Ephemeral container, persistent data".

Raises:

  • (RuntimeError)

    on missing docker, any docker command failure, or healthcheck timeout.



103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/pikuri/vector_db/server/docker_container.rb', line 103

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