class Cutlass::ContainerBoot

Boots containers and tears 'em down

Has a single method ContainerBoot#call which returns an instance of

boot = ContainerBoot.new(image_id: @image.id)
boot.call do |container_control|
  container_control.class # => ContainerControl
  container_control.bash_exec("pwd")
end

The number one reason to boot a container is to be able to exercise a booted server from within the container. To do this you need to tell docker want port to expose inside of the container. Docker will expose that port and bind it to a free port on the “host” i.e. your local machine. From there you can make queries to various docker ports:

boot = ContainerBoot.new(image_id: @image.id, expose_ports: [8080])
boot.call do |container_control|
  local_port = container_control.get_host_port(8080)

  `curl localhost:#{local_port}`
end

Note: Booting a container only works if the image has an ENTRYPOINT that does not exit.

Note: Running `bash_exec` commands from this context gives you a raw access to the container. It does not execute the container's entrypoint. That means if you're running inside of a CNB image, that env vars won't be set and the directory might be different.

Public Class Methods

new(image_id:, expose_ports: []) click to toggle source
# File lib/cutlass/container_boot.rb, line 36
def initialize(image_id:, expose_ports: [])
  @expose_ports = Array(expose_ports)
  config = {
    "Image" => image_id,
    "ExposedPorts" => {},
    "HostConfig" => {
      "PortBindings" => {}
    }
  }

  port_bindings = config["HostConfig"]["PortBindings"]

  @expose_ports.each do |port|
    config["ExposedPorts"]["#{port}/tcp"] = {}

    # If we do not specify a port, Docker will grab a random unused one:
    port_bindings["#{port}/tcp"] = [{"HostPort" => ""}]
  end

  @container = Docker::Container.create(config)
end

Public Instance Methods

call() { |container_control(container, ports: expose_ports)| ... } click to toggle source
# File lib/cutlass/container_boot.rb, line 58
    def call
      raise "Must call with a block" unless block_given?

      @container.start!

      puts @container.logs(stdout: 1) if Cutlass.debug?
      puts @container.logs(stderr: 1) if Cutlass.debug?

      yield ContainerControl.new(@container, ports: @expose_ports)
    rescue => error
      raise error, <<~EOM
        message #{error.message}

        boot stdout: #{@container.logs(stdout: 1)}
        boot stderr: #{@container.logs(stderr: 1)}
      EOM
    ensure
      @container&.delete(force: true)
    end