Class: Docker::API::Container

Inherits:
Resource
  • Object
show all
Defined in:
lib/docker/api/resources/container.rb

Overview

A container on the daemon.

Examples:

Run a command and read its output

container = client.containers.get("web")
result = container.exec(["cat", "/etc/hostname"])
result.stdout #=> "3f2a9c1b0e4d\n"

Follow logs

container.logs(follow: true) { |stream, chunk| $stdout << chunk }

Instance Attribute Summary

Attributes inherited from Resource

#client, #raw

Instance Method Summary collapse

Methods inherited from Resource

#==, #[], #hash, #id, #initialize, #partial?, #stale?, #to_s

Constructor Details

This class inherits a constructor from Docker::API::Resource

Instance Method Details

#archive_in(archive, path:, overwrite_non_directory: true, copy_uid_gid: false) ⇒ self

Copy a tar archive into the container.

Parameters:

  • archive (String, IO)

    tar bytes, or an IO to stream from

  • path (String)

    the destination directory inside the container

  • overwrite_non_directory (Boolean) (defaults to: true)

    allow replacing a file with a directory, or the reverse

  • copy_uid_gid (Boolean) (defaults to: false)

    keep the archive's ownership

Returns:

  • (self)


263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/docker/api/resources/container.rb', line 263

def archive_in(archive, path:, overwrite_non_directory: true, copy_uid_gid: false)
  operations.put_container_archive(
    id: id, path: path,
    # The content type is not a parameter this endpoint declares, so it
    # is not a keyword the generated layer accepts; the connection
    # labels raw bodies as archives, which is what this one is.
    #
    # An IO is handed over as it stands rather than read into a String.
    # Slurping defeated the point of accepting one: a container
    # filesystem is exactly the kind of archive nobody wants resident in
    # memory, and the connection streams a readable body chunked.
    body: archive,
    no_overwrite_dir_non_dir: !overwrite_non_directory,
    copy_uidgid: copy_uid_gid
  )
  self
end

#archive_out(path) {|chunk| ... } ⇒ String, self

Read a path out of the container as a tar archive.

Parameters:

  • path (String)

    the path inside the container

Yield Parameters:

  • chunk (String)

    tar bytes, when a block is given

Returns:

  • (String, self)

    the archive, or self when streamed



286
287
288
289
290
291
# File 'lib/docker/api/resources/container.rb', line 286

def archive_out(path, &block)
  return operations.container_archive(id: id, path: path).body unless block

  operations.container_archive(id: id, path: path, &block)
  self
end

#attach(stdin: false, stdout: true, stderr: true, logs: false) ⇒ IO

Attach to the container's streams, taking over the socket.

Parameters:

  • stdin (Boolean) (defaults to: false)

    attach the input stream

  • stdout (Boolean) (defaults to: true)

    attach standard output

  • stderr (Boolean) (defaults to: true)

    attach standard error

  • logs (Boolean) (defaults to: false)

    replay existing output first

Returns:

  • (IO)

    the bidirectional stream



246
247
248
249
250
251
252
253
# File 'lib/docker/api/resources/container.rb', line 246

def attach(stdin: false, stdout: true, stderr: true, logs: false)
  client.connection.hijack(
    :post, "/containers/#{Path.escape(id)}/attach",
    query: { "stream" => true, "stdin" => stdin, "stdout" => stdout,
             "stderr" => stderr, "logs" => logs },
    operation: "container_attach"
  )
end

#commit(repo: nil, tag: nil, comment: nil, author: nil, pause: true) ⇒ Docker::API::Image

Turn the container's filesystem into an image.

Parameters:

  • repo (String, nil) (defaults to: nil)

    the repository to name it

  • tag (String, nil) (defaults to: nil)

    the tag to give it

  • comment (String, nil) (defaults to: nil)

    a commit message

  • author (String, nil) (defaults to: nil)

    who made it

  • pause (Boolean) (defaults to: true)

    pause the container while committing

Returns:



301
302
303
304
305
306
307
# File 'lib/docker/api/resources/container.rb', line 301

def commit(repo: nil, tag: nil, comment: nil, author: nil, pause: true)
  response = operations.image_commit(
    container: id, repo: repo, tag: tag, comment: comment,
    author: author, pause: pause
  )
  client.images.get(response.json!["Id"])
end

#exec(command, env: {}, user: nil, working_dir: nil, tty: false, privileged: false) {|stream, chunk| ... } ⇒ Docker::API::ExecResult

Run a command inside the container and wait for it to finish.

Examples:

result = container.exec(%w{chef-client -z}) { |_stream, chunk| logger << chunk }
raise "converge failed" unless result.success?

Parameters:

  • command (Array<String>, String)

    the command and its arguments

  • env (Hash) (defaults to: {})

    environment variables for the command

  • user (String, nil) (defaults to: nil)

    the user to run as

  • working_dir (String, nil) (defaults to: nil)

    the directory to run in

  • tty (Boolean) (defaults to: false)

    allocate a TTY, which un-multiplexes the output

  • privileged (Boolean) (defaults to: false)

    run privileged

Yield Parameters:

  • stream (Symbol)

    :stdout or :stderr

  • chunk (String)

Returns:



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/docker/api/resources/container.rb', line 213

def exec(command, env: {}, user: nil, working_dir: nil, tty: false,
  privileged: false, &block)
  exec_id = create_exec(command, env, user, working_dir, tty, privileged)
  stdout = +""
  stderr = +""

  sink = lambda do |stream, chunk|
    (stream == :stderr ? stderr : stdout) << chunk
    block&.call(stream, chunk)
  end
  decoder = tty ? Stream::Raw.new { |chunk| sink.call(:stdout, chunk) } : Stream::Demultiplexer.new(&sink)

  operations.exec_start(
    id: exec_id, body: { "Detach" => false, "Tty" => tty }
  ) { |chunk| decoder << chunk }

  ExecResult.new(
    stdout: stdout, stderr: stderr,
    # Not .to_i. The daemon reports "ExitCode": null while an exec is
    # still being reaped, and nil.to_i is 0 -- so a command whose result
    # was not yet known reported success, and #success? agreed. nil
    # travels through instead, and #success? is false for it.
    exit_code: operations.exec_inspect(id: exec_id).json!["ExitCode"]
  )
end

#imageString?

Returns the image the container was created from, by the name it was requested under rather than by digest.

Returns:

  • (String, nil)

    the image the container was created from, by the name it was requested under rather than by digest



42
43
44
45
46
47
# File 'lib/docker/api/resources/container.rb', line 42

def image
  # An inspect puts the friendly name in Config.Image and a digest in
  # Image; a list puts the friendly name in Image. Preferring
  # Config.Image gets the readable answer from both without a round trip.
  detail("Config.Image", "Image")
end

#ip_addressString?

Returns the container's address on its primary network.

Returns:

  • (String, nil)

    the container's address on its primary network



72
73
74
75
# File 'lib/docker/api/resources/container.rb', line 72

def ip_address
  networks.each_value { |net| return net["IPAddress"] unless net["IPAddress"].to_s.empty? }
  detail("NetworkSettings.IPAddress")
end

#kill(signal: nil) ⇒ self

Parameters:

  • signal (String, nil) (defaults to: nil)

    the signal to send, SIGKILL by default

Returns:

  • (self)


112
113
114
115
# File 'lib/docker/api/resources/container.rb', line 112

def kill(signal: nil)
  operations.container_kill(id: id, signal: signal)
  mark_stale
end

#labelsHash

Returns the container's labels.

Returns:

  • (Hash)

    the container's labels



50
51
52
# File 'lib/docker/api/resources/container.rb', line 50

def labels
  detail("Config.Labels", "Labels") || {}
end

#logs(follow: false, stdout: true, stderr: true, tail: nil, since: nil, timestamps: false) {|stream, chunk| ... } ⇒ String, self

Read the container's output.

Without a block the whole log is returned as a string. With a block, chunks are yielded as they arrive, demultiplexed into named streams unless the container has a TTY, in which case the daemon sends one undifferentiated stream and every chunk is reported as :stdout.

Parameters:

  • follow (Boolean) (defaults to: false)

    keep streaming as new output appears

  • stdout (Boolean) (defaults to: true)

    include stdout

  • stderr (Boolean) (defaults to: true)

    include stderr

  • tail (String, Integer, nil) (defaults to: nil)

    how many trailing lines to start with

  • since (Integer, nil) (defaults to: nil)

    a UNIX timestamp to start from

  • timestamps (Boolean) (defaults to: false)

    prefix every line with its timestamp

Yield Parameters:

  • stream (Symbol)

    :stdout or :stderr

  • chunk (String)

Returns:

  • (String, self)

    the log, or self when a block was given



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/docker/api/resources/container.rb', line 180

def logs(follow: false, stdout: true, stderr: true, tail: nil,
  since: nil, timestamps: false, &block)
  # Defaulted rather than fixed keys: Demultiplexer maps a frame id it
  # does not recognise to :unknown, and a fixed two-key hash turned that
  # into `undefined method '<<' for nil` -- one corrupt frame crashing a
  # log read, with a bare NoMethodError rather than a Docker::API::Error.
  collected = Hash.new { |streams, name| streams[name] = +"" }
  sink = block || ->(stream, chunk) { collected[stream] << chunk }
  decoder = tty? ? Stream::Raw.new { |chunk| sink.call(:stdout, chunk) } : Stream::Demultiplexer.new(&sink)

  operations.container_logs(
    id: id, follow: follow, stdout: stdout, stderr: stderr,
    tail: tail&.to_s, since: since, timestamps: timestamps
  ) { |chunk| decoder << chunk }

  block ? self : collected[:stdout] + collected[:stderr]
end

#nameString?

Returns the container's name, without the leading slash the daemon puts on it. Answers identically whether this object came from a list or an inspect.

Returns:

  • (String, nil)

    the container's name, without the leading slash the daemon puts on it. Answers identically whether this object came from a list or an inspect.



21
22
23
24
25
26
27
# File 'lib/docker/api/resources/container.rb', line 21

def name
  # "Name" comes from an inspect, "Names" from a list. Both are tried
  # against the payload in hand before any request is made.
  value = detail("Name", "Names")
  value = Array(value).first if value.is_a?(Array)
  value&.sub(%r{\A/}, "")
end

#networksHash{String => Hash}

Returns networks this container is attached to.

Returns:

  • (Hash{String => Hash})

    networks this container is attached to



67
68
69
# File 'lib/docker/api/resources/container.rb', line 67

def networks
  detail("NetworkSettings.Networks") || {}
end

#pauseself

Returns:

  • (self)


118
119
120
121
# File 'lib/docker/api/resources/container.rb', line 118

def pause
  operations.container_pause(id: id)
  mark_stale
end

#portsArray<Hash>

Published ports, in one shape regardless of where the payload came from. A list response reports an array; an inspect response reports a map keyed by port. Both become the same array of hashes here.

Returns:

  • (Array<Hash>)

    with :port, :protocol, :host_ip and :host_port



59
60
61
62
63
64
# File 'lib/docker/api/resources/container.rb', line 59

def ports
  listed = raw["Ports"]
  return normalize_listed_ports(listed) if listed.is_a?(Array)

  normalize_inspected_ports(detail("NetworkSettings.Ports") || {})
end

#reloadself

Re-read this container from the daemon.

Returns:

  • (self)


86
87
88
# File 'lib/docker/api/resources/container.rb', line 86

def reload
  replace_raw(operations.container_inspect(id: id || name).json)
end

#remove(force: false, volumes: false, link: false) ⇒ void

This method returns an undefined value.

Parameters:

  • force (Boolean) (defaults to: false)

    remove even if running

  • volumes (Boolean) (defaults to: false)

    remove anonymous volumes too

  • link (Boolean) (defaults to: false)

    remove the specified link



140
141
142
143
# File 'lib/docker/api/resources/container.rb', line 140

def remove(force: false, volumes: false, link: false)
  operations.container_delete(id: id, force: force, v: volumes, link: link)
  nil
end

#rename(name) ⇒ self

Parameters:

  • name (String)

    the new name

Returns:

  • (self)


131
132
133
134
# File 'lib/docker/api/resources/container.rb', line 131

def rename(name)
  operations.container_rename(id: id, name: name)
  reload
end

#restart(timeout: nil, signal: nil) ⇒ self

Parameters:

  • timeout (Integer, nil) (defaults to: nil)

    seconds to wait before killing

  • signal (String, nil) (defaults to: nil)

    the signal to send first

Returns:

  • (self)


106
107
108
# File 'lib/docker/api/resources/container.rb', line 106

def restart(timeout: nil, signal: nil)
  idempotently { operations.container_restart(id: id, t: timeout, signal: signal) }
end

#running?Boolean

Returns whether the container is running right now.

Returns:

  • (Boolean)

    whether the container is running right now



36
37
38
# File 'lib/docker/api/resources/container.rb', line 36

def running?
  state == "running"
end

#start(detach_keys: nil) ⇒ self

Parameters:

  • detach_keys (String, nil) (defaults to: nil)

    the key sequence that detaches

Returns:

  • (self)


92
93
94
# File 'lib/docker/api/resources/container.rb', line 92

def start(detach_keys: nil)
  idempotently { operations.container_start(id: id, detach_keys: detach_keys) }
end

#stateString?

Returns "running", "exited", "created", and so on.

Returns:

  • (String, nil)

    "running", "exited", "created", and so on



30
31
32
33
# File 'lib/docker/api/resources/container.rb', line 30

def state
  value = detail("State")
  value.is_a?(Hash) ? value["Status"] : value
end

#statsHash

Returns a single resource-usage sample.

Returns:

  • (Hash)

    a single resource-usage sample



160
161
162
# File 'lib/docker/api/resources/container.rb', line 160

def stats
  operations.container_stats(id: id, stream: false, one_shot: true).json
end

#stop(timeout: nil, signal: nil) ⇒ self

Parameters:

  • timeout (Integer, nil) (defaults to: nil)

    seconds to wait before killing

  • signal (String, nil) (defaults to: nil)

    the signal to send first

Returns:

  • (self)


99
100
101
# File 'lib/docker/api/resources/container.rb', line 99

def stop(timeout: nil, signal: nil)
  idempotently { operations.container_stop(id: id, t: timeout, signal: signal) }
end

#top(ps_args: nil) ⇒ Hash

Returns with "Titles" and "Processes".

Parameters:

  • ps_args (String, nil) (defaults to: nil)

    arguments passed to ps inside the container

Returns:

  • (Hash)

    with "Titles" and "Processes"



155
156
157
# File 'lib/docker/api/resources/container.rb', line 155

def top(ps_args: nil)
  operations.container_top(id: id, ps_args: ps_args).json
end

#tty?Boolean

Returns whether the container was created with a TTY, which decides whether its output stream is multiplexed.

Returns:

  • (Boolean)

    whether the container was created with a TTY, which decides whether its output stream is multiplexed



79
80
81
# File 'lib/docker/api/resources/container.rb', line 79

def tty?
  detail("Config.Tty") == true
end

#unpauseself

Returns:

  • (self)


124
125
126
127
# File 'lib/docker/api/resources/container.rb', line 124

def unpause
  operations.container_unpause(id: id)
  mark_stale
end

#wait(condition: nil) ⇒ Integer

Block until the container stops.

Parameters:

  • condition (String, nil) (defaults to: nil)

    "not-running", "next-exit" or "removed"

Returns:

  • (Integer)

    the container's exit code



149
150
151
# File 'lib/docker/api/resources/container.rb', line 149

def wait(condition: nil)
  operations.container_wait(id: id, condition: condition).json!["StatusCode"]
end