{"id":13416514,"url":"https://github.com/into-docker/clj-docker-client","last_synced_at":"2025-03-15T00:31:07.746Z","repository":{"id":54159028,"uuid":"156752607","full_name":"into-docker/clj-docker-client","owner":"into-docker","description":"An idiomatic, data-driven, REPL friendly Clojure Docker client","archived":false,"fork":false,"pushed_at":"2022-10-12T10:09:04.000Z","size":1290,"stargazers_count":175,"open_issues_count":4,"forks_count":13,"subscribers_count":9,"default_branch":"main","last_synced_at":"2024-08-07T04:02:47.805Z","etag":null,"topics":["clojure","docker","docker-api","docker-client","jvm"],"latest_commit_sha":null,"homepage":"https://cljdoc.org/d/lispyclouds/clj-docker-client/CURRENT","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/into-docker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-08T18:40:47.000Z","updated_at":"2024-05-31T07:43:49.000Z","dependencies_parsed_at":"2022-08-13T08:00:17.829Z","dependency_job_id":null,"html_url":"https://github.com/into-docker/clj-docker-client","commit_stats":null,"previous_names":["lispyclouds/clj-docker-client"],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/into-docker%2Fclj-docker-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/into-docker%2Fclj-docker-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/into-docker%2Fclj-docker-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/into-docker%2Fclj-docker-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/into-docker","download_url":"https://codeload.github.com/into-docker/clj-docker-client/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243667714,"owners_count":20328032,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","docker","docker-api","docker-client","jvm"],"created_at":"2024-07-30T21:01:00.013Z","updated_at":"2025-03-15T00:31:06.833Z","avatar_url":"https://github.com/into-docker.png","language":"Clojure","readme":"## clj-docker-client [![](https://github.com/lispyclouds/clj-docker-client/workflows/Tests/badge.svg)](https://github.com/lispyclouds/clj-docker-client/actions?query=workflow%3ATests)\n\n[![License: LGPL v3](https://img.shields.io/badge/license-LGPL%20v3-blue.svg?style=flat-square)](http://www.gnu.org/licenses/lgpl-3.0)\n[![Clojars Project](https://img.shields.io/clojars/v/lispyclouds/clj-docker-client.svg?style=flat-square)](https://clojars.org/lispyclouds/clj-docker-client)\n\n[![cljdoc badge](https://cljdoc.org/badge/lispyclouds/clj-docker-client)](https://cljdoc.org/d/lispyclouds/clj-docker-client/CURRENT)\n[![Downloads](https://versions.deps.co/lispyclouds/clj-docker-client/downloads.svg)](https://versions.deps.co/lispyclouds/clj-docker-client)\n\n[![project chat](https://img.shields.io/badge/slack-join_chat-brightgreen.svg)](https://clojurians.slack.com/messages/C0PME9N9X)\n\nAn idiomatic, data-driven, REPL friendly Clojure Docker client inspired from Cognitect's AWS [client](https://github.com/cognitect-labs/aws-api).\n\n#### DEPRECATION NOTICE: [contajners](https://github.com/lispyclouds/contajners) is recommended over this library. Supports more container engines like Podman and has a simpler and more efficient design and would receive new updates and maintainence. 😄\n\nSee [this](https://cljdoc.org/d/lispyclouds/clj-docker-client/0.3.2/doc/readme) for documentation for versions before **0.4.0**.\n\n**The README here is for the current master branch and _may not reflect the released version_.**\n\n**Please raise issues here for any new feature requests!**\n\n### Installation\nLeiningen/Boot\n```clojure\n[lispyclouds/clj-docker-client \"1.0.3\"]\n```\n\nClojure CLI/deps.edn\n```clojure\n{lispyclouds/clj-docker-client {:mvn/version \"1.0.3\"}}\n```\n\nGradle\n```groovy\ncompile 'lispyclouds:clj-docker-client:1.0.3'\n```\n\nMaven\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003elispyclouds\u003c/groupId\u003e\n  \u003cartifactId\u003eclj-docker-client\u003c/artifactId\u003e\n  \u003cversion\u003e1.0.3\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Build Requirements\n- Leiningen 2.8+\n- JDK 1.8+\n\n### Running tests locally\n- Install [leiningen](https://leiningen.org/)\n- Install [Docker](https://www.docker.com/)\n- `lein kaocha` to run all tests. (needs Docker and working internet)\n\nAuto generated code docs can be found [here](https://cljdoc.org/d/lispyclouds/clj-docker-client/CURRENT)\n\n### Developing with [Reveal](https://vlaaad.github.io/reveal/) and Leiningen\nSince this is fully data driven, using Reveal is really beneficial as it allows us to _walk_ through the output from Docker, see potential errors and be more productive with instant visual feedback.\n\n- Clone this repo.\n- In the root, start the leiningen REPL with: `lein with-profile +reveal repl`. This fires up the the Reveal UI alongside the usual REPL.\n- Connect your editor of choice to this REPL or start using Reveal REPL directly.\n- Then repeat after me 3 times: _ALL HAIL THE DATA_! 🙏🏽\n\n### The Docker API\nThis uses Docker's HTTP REST API to run. See the API [version matrix](https://docs.docker.com/engine/api/#api-version-matrix) to find the corresponding API version for the Docker daemon you're running.\n\nclj-docker-client works by parsing the Swagger 2.0 YAMLs from the docker client API and vendors it in this [directory](https://github.com/into-docker/clj-docker-client/tree/master/resources/clj_docker_client/api). **This defaults to using the latest version available there if no versions are pinned.** It is recommended to use a pinned version to have consistent behavior across different engine versions.\n\nSee the [page](https://docs.docker.com/develop/sdk/) about the docker REST API to learn more about the usage and params to pass.\n\n### Usage\n\n```clojure\n(require '[clj-docker-client.core :as docker])\n```\n\nThis library aims to be a _as thin layer as possible_ between you and Docker. This consists of following public functions:\n\n#### categories\n\nLists the categories of operations supported. Can be bound to an API version.\n```clojure\n(docker/categories) ; Latest version\n\n(docker/categories \"v1.40\") ; Locked to v1.40\n\n#_=\u003e #{:system\n       :exec\n       :images\n       :secrets\n       :events\n       :_ping\n       :containers\n       :auth\n       :tasks\n       :volumes\n       :networks\n       :build\n       :nodes\n       :commit\n       :plugins\n       :info\n       :swarm\n       :distribution\n       :version\n       :services\n       :configs\n       :session}\n```\n\n#### client\n\nConnect to the docker daemon's [UNIX socket](https://en.wikipedia.org/wiki/Unix_domain_socket) and\ncreate a client scoped to the operations of a given category. Can be bound to an API version.\n```clojure\n(def images (docker/client {:category :images\n                            :conn     {:uri \"unix:///var/run/docker.sock\"}})) ; Latest version\n\n(def containers (docker/client {:category    :containers\n                                :conn        {:uri \"unix:///var/run/docker.sock\"}\n                                :api-version \"v1.40\"})) ; Container client for v1.40\n```\nUsing a timeout for the connections. Thanks [olymk2](https://github.com/olymk2) for the suggestion.\nDocker actions can take quite a long time so set the timeout accordingly. When you don't provide timeouts\nthen there will be no timeout on the client side.\n```clojure\n(def ping (docker/client {:category :_ping\n                          :conn     {:uri      \"unix:///var/run/docker.sock\"\n                                     :timeouts {:connect-timeout 10\n                                                :read-timeout    30000\n                                                :write-timeout   30000\n                                                :call-timeout    30000}}}))\n```\nAlternatively if connecting to a remote docker daemon over TCP supply the `:uri` as `http://your.docker.host:2376`. **NOTE**: `unix://`, `http://`, `tcp://` and `https://` are the currently supported protocols.\n\n#### ops\nLists the supported ops by a client.\n```clojure\n(docker/ops images)\n\n#_=\u003e (:ImageList\n      :ImageCreate\n      :ImageInspect\n      :ImageHistory\n      :ImagePush\n      :ImageTag\n      :ImageDelete\n      :ImageSearch\n      :ImagePrune\n      :ImageGet\n      :ImageGetAll\n      :ImageLoad)\n```\n\n#### doc\nReturns the doc of an operation in a client.\n```clojure\n(docker/doc images :ImageList)\n\n#_=\u003e {:doc\n      \"List Images\\nReturns a list of images on the server. Note that it uses a different, smaller representation of an image than inspecting a single image.\",\n      :params\n      ({:name \"all\", :type \"boolean\"}\n       {:name \"filters\", :type \"string\"}\n       {:name \"digests\", :type \"boolean\"})}\n```\n\n#### invoke\nInvokes an operation via the client and a given operation map and returns the result data.\n```clojure\n; Pulls the busybox:musl image from Docker hub\n(docker/invoke images {:op     :ImageCreate\n                       :params {:fromImage \"busybox:musl\"}})\n\n; Creates a container named conny from it\n(docker/invoke containers {:op     :ContainerCreate\n                           :params {:name \"conny\"\n                                    :body {:Image \"busybox:musl\"\n                                           :Cmd   \"ls\"}}})\n```\n\nThe operation map is of the following structure:\n```clojure\n{:op     :NameOfOp\n :params {:param-1 \"value1\"\n          :param-2 true}}\n```\nTakes an optional key `as`. Defaults to `:data`. Returns an InputStream if passed as `:stream`, the raw underlying network socket if passed as `:socket`. `:stream` is useful for streaming responses like logs, events etc, which run till the container is up. `:socket` is useful for events when bidirectional streams are returned by docker in operations like `:ContainerAttach`.\n```clojure\n{:op     :NameOfOp\n :params {:param-1 \"value1\"\n          :param-2 true}\n :as     :stream}\n```\n\nTakes another optional key `:throw-exception?`. Defaults to `false`. If set to true will throw an exception for exceptional status codes from the Docker API i.e. `status \u003e= 400`. Throws an `java.lang.RuntimeException` with the message.\n```clojure\n{:op               :NameOfOp\n :throw-exception? true}\n```\n\n### General guidelines\n- Head over to the Docker API docs to get more info on the type of parameters you should be sending. eg: this [page](https://docs.docker.com/engine/api/v1.40/) for `v1.40` API docs.\n- The type `stream` is mapped to `java.io.InputStream` and when the API needs a stream as an input, send an InputStream. When it returns a stream, the call can **possibly block** till the container or source is up and it's recommended to pass the `as` param as `:stream` to the invoke call and read it asynchronously. See this [section](https://github.com/lispyclouds/clj-docker-client/tree/master#streaming-logs) for more info.\n\n### Sample code for common scenarios\n\n#### Pulling an image\n```clojure\n(def images (docker/client {:category :images\n                            :conn     {:uri \"unix:///var/run/docker.sock\"}}))\n\n(docker/invoke images {:op     :ImageCreate\n                       :params {:fromImage \"busybox:musl\"}})\n```\n\n#### Creating a container\n```clojure\n(def containers (docker/client {:category :containers\n                                :conn     {:uri \"unix:///var/run/docker.sock\"}}))\n\n(docker/invoke containers {:op     :ContainerCreate\n                           :params {:name \"conny\"\n                                    :body {:Image \"busybox:musl\"\n                                           :Cmd   [\"sh\"\n                                                   \"-c\"\n                                                   \"i=1; while :; do echo $i; sleep 1; i=$((i+1)); done\"]}}})\n```\n\n#### Starting a container\n```clojure\n\n(docker/invoke containers {:op     :ContainerStart\n                           :params {:id \"conny\"}})\n```\n\n#### Creating a network\n```clojure\n\n(def networks (docker/client {:category    :networks\n                              :conn        {:uri \"unix:///var/run/docker.sock\"}\n                              :api-version \"v1.40\"}))\n\n(docker/invoke networks {:op     :NetworkCreate\n                         :params {:networkConfig {:Name \"conny-network\"}}})\n```\n\n#### Streaming logs\n```clojure\n; fn to react when data is available\n(defn react-to-stream\n  [stream reaction-fn]\n  (future\n    (with-open [rdr (clojure.java.io/reader stream)]\n      (loop [r (java.io.BufferedReader. rdr)]\n        (when-let [line (.readLine r)]\n          (reaction-fn line)\n          (recur r))))))\n\n(def log-stream (docker/invoke containers {:op     :ContainerLogs\n                                           :params {:id     \"conny\"\n                                                    :follow true\n                                                    :stdout true}\n                                           :as     :stream}))\n\n(react-to-stream log-stream println) ; prints the logs line by line when they come.\n```\n\n#### Attach to a container and send data to stdin\n```clojure\n;; This is a raw bidirectional java.net.Socket, so both reads and writes are possible.\n;; conny-reader has been started with: docker run -d -i --name conny-reader alpine:latest sh -c \"cat - \u003e/out\"\n(def sock (docker/invoke containers {:op     :ContainerAttach\n                                     :params {:id     \"conny-reader\"\n                                              :stream true\n                                              :stdin  true}\n                                     :as     :socket}))\n\n(clojure.java.io/copy \"hello\" (.getOutputStream sock))\n\n(.close sock) ; Important for freeing up resources.\n```\n\n#### Using registries that need authentication\nThanks [@AustinC](https://github.com/AustinC) for this example.\n\n```clojure\n(ns dclj.core\n  (:require [clj-docker-client.core :as d]\n            [cheshire.core :as json])\n  (:import [java.util Base64]))\n\n(defn b64-encode\n  [to-encode]\n  (.encodeToString (Base64/getEncoder) (.getBytes to-encode)))\n\n(def auth\n  (-\u003e {\"username\"      \"un\"\n       \"password\"      \"pw\"\n       \"serveraddress\" \"docker.acme.com\"}\n      json/encode\n      b64-encode))\n\n(def images\n  (d/client {:category    :images\n             :conn        {:uri \"unix:///var/run/docker.sock\"}\n             :api-version \"v1.40\"}))\n\n(d/invoke images\n          {:op               :ImageCreate\n           :params           {:fromImage       \"docker.acme.com/eg:2.1.995\"\n                              :X-Registry-Auth auth}\n           :throw-exception? true})\n```\n\n#### HTTPS and Mutual TLS(mTLS)\n\nSince both https and unix sockets are suppported, and generally docker deamons exposed over HTTPS are protected via [mTLS](https://docs.docker.com/engine/security/protect-access/#use-tls-https-to-protect-the-docker-daemon-socket), here is an example using mTLS to connect to docker via HTTPS:\n\n```clojure\n;; Create a client using https\n;; The ca.pem, key.pem and cert.pem are produced by the docker daemon when protected via mTLS\n(def http-tls-ping\n  (client {:category :_ping\n           :conn     {:uri  \"https://my.remote.docker.host:8000\"\n                      :mtls {:ca   \"ca.pem\"\n                             :key  \"key.pem\"\n                             :cert \"cert.pem\"}}}))\n\n(invoke http-tls-ping {:op :SystemPing}) ;; =\u003e Returns \"OK\"\n```\n\nThe caveat here is _password protected PEM files aren't supported yet_. Please raise an issue if there is a need for it.\n\n### Not so common scenarios\n\n#### Accessing undocumented/experimental Docker APIs\nThere are some cases where you may need access to an API that is either experimental or is not in the swagger docs.\nDocker [checkpoint](https://docs.docker.com/engine/reference/commandline/checkpoint/) is one such example. Thanks [@mk](https://github.com/mk) for bringing it up!\n\nSince this uses the published APIs from the swagger spec, the way to access them is to use the lower level fn `fetch` from the `clj-docker-client/requests` ns. The caveat is the **response will be totally raw(data, stream or the socket itself)**.\n\nfetch takes the following params as a map:\n- conn: the connection to the daemon. Required.\n- url: the relative path to the operation. Required.\n- method: the method of the HTTP request as a keyword. Default: `:get`.\n- query: the map of key-values to be passed as query params.\n- path: the map of key-values to be passed as path params. Needed for interpolated path values like `/v1.40/containers/{id}/checkpoints`. Pass `{:id \"conny\"}` here.\n- header: the map of key-values to be passed as HEADER params.\n- body: the stream or map(will be converted to JSON) to be passed as body.\n- as: takes the kind of response expected. One of :stream, :socket or :data. Same as `invoke`. Default: `:data`.\n\n```clojure\n(require '[clj-docker-client.requests :as req])\n(require '[clj-docker-client.core :as docker])\n\n;; This is the undocumented API in the Docker Daemon.\n;; See https://github.com/moby/moby/pull/22049/files#diff-8038ade87553e3a654366edca850f83dR11\n(req/fetch {:conn (req/connect* {:uri \"unix:///var/run/docker.sock\"})\n            :url  \"/v1.40/containers/conny/checkpoints\"})\n```\n\nMore examples of low level calls:\n```clojure\n;; Ping the server\n(req/fetch {:conn (req/connect* {:uri \"unix:///var/run/docker.sock\"})\n            :url  \"/v1.40/_ping\"})\n\n;; Copy a folder to a container\n(req/fetch {:conn   (req/connect* {:uri \"unix:///var/run/docker.sock\"})\n            :url    \"/v1.40/containers/conny/archive\"\n            :method :put\n            :query  {:path \"/root/src\"}\n            :body   (-\u003e \"src.tar.gz\"\n                        io/file\n                        io/input-stream)})\n```\n\n#### Reading a streaming output in case of an exception being thrown\n\nWhen `:throw-exception?` is passed as `true` and the `:as` is set to `:stream`, to read the response stream, pass `throw-entire-message?` as `true` to the invoke. The stream is available as `:body` in the ex-data of the exception.\n```clojure\n(try\n  (invoke containers\n          {:op                    :ContainerArchive\n           :params                {:id   \"conny\"\n                                   :path \"/this-does-not-exist\"}\n           :as                    :stream\n           :throw-exception?      true\n           :throw-entire-message? true})\n  (catch Exception e\n    (-\u003e e ex-data :body slurp println))) ; Prints out the body of error from docker.\n```\n\nAnd anything else is possible!\n\n## License\n\nCopyright © 2020 Rahul De and [contributors](https://github.com/lispyclouds/clj-docker-client/graphs/contributors).\n\nDistributed under the LGPLv3+ License. See LICENSE.\n","funding_links":[],"categories":["Development with Docker"],"sub_categories":["API Client"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finto-docker%2Fclj-docker-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finto-docker%2Fclj-docker-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finto-docker%2Fclj-docker-client/lists"}