Skip to content

jvassev/image2ipfs

Repository files navigation

Introduction

This project teaches Docker some IPFS. IPFS is a global, versioned, peer-to-peer filesystem. IPFS combines good ideas from Git, BitTorrent, Kademlia, SFS, and the Web. It is like a single bittorrent swarm, exchanging git objects. [https://github.com/ipfs/go-ipfs]

image2ipfs works only with images prduced docker >= 1.10. On the bright side, all post-1.6 dockers can pull from an ipfs-registry.

How does it work?

image2ipfs takes an image archive produced using docker save. The archive is extracted to a temp location then processed a bit. Finally, it is added to IPFS using ipfs add -r (you need to have the ipfs binary in your PATH). For a simple busybox image image2ipfs will produce something like this:

busybox
├── blobs
│   ├── sha256:193bda8d9ac77416619eb556391a9c5447adb2abf12aab515d1b0c754637eb80
│   ├── sha256:47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb
│   └── sha256:a1b1b81d3d1afdb8fe119b002318c12c20934713b9754a40f702adb18a2540b9
└── manifests
    └── latest-v2

v1 Manifests are not supported starting with version 0.0.6 But how do you get from something like QmQSF1oN4TXeU2kRvmjerEs62ZYfKPyRCqPvW1XTTc4fLS to a working docker pull busybox?

The answer is simple: using url rewriting.There is a trivial Go application that speaks the Registry v2 protocol but instead of serving the blobs and manifests it redirects to an IPFS gateway of your choice.

When pulling REPO:latest (with REPO=busybox in this example), Docker daemon will issue (roughly) these requests:

GET /v2/
GET /v2/REPO/manifest/latest
GET /v2/REPO/blobs/sha256:47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb
GET /v2/REPO/blobs/sha256:193bda8d9ac77416619eb556391a9c5447adb2abf12aab515d1b0c754637eb80
GET /v2/REPO/blobs/sha256:a1b1b81d3d1afdb8fe119b002318c12c20934713b9754a40f702adb18a2540b9

The Go app serves redirects to an IPFS gateway like so:

GET /ipfs/HASH/manifest/latest
GET /ipfs/HASH/blobs/sha256:47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb
GET /ipfs/HASH/blobs/sha256:193bda8d9ac77416619eb556391a9c5447adb2abf12aab515d1b0c754637eb80
GET /ipfs/HASH/blobs/sha256:a1b1b81d3d1afdb8fe119b002318c12c20934713b9754a40f702adb18a2540b9

Installation

$ git clone https://github.com/jvassev/image2ipfs

# this will install a python command so pyenv or virtualenv is recommended rather than sudo
$ make install

$ image2ipfs --version
0.1.0

Running the registry

You can pull the official image from dockerhub. This example assumes the gateway is running on localhost. Consider using another gateway address as image2ipfs will serve redirects to it.

docker run -td --name ipfs-registry -e IPFS_GATEWAY=http://localhost:8080 --net host jvassev/ipfs-registry

Or build from source and skip Docker:

$ make install

$ image2ipfs server
2019/03/12 15:43:58 Using IPFS gateway http://127.0.0.1:8080
2019/03/12 15:43:58 Serving on :5000

Synopsis

The client and server live in the same binary. You can find prebuilt releases for Linux, Mac and Windows on the Releases page. The command flags are compatible with the legacy Python version.

usage: image2ipfs [<flags>] <command> [<args> ...]

Flags:
      --help       Show context-sensitive help (also try --help-long and --help-man).
      --version    Show application version.
  -r, --registry="http://localhost:5000"
                   Registry to use when generating pull URL
  -n, --no-add     Don`t add to IPFS, just print directory
  -q, --quiet      Produce less output - just the final pullable image name
  -d, --debug      Leave workdir intact (useful for debugging)
  -i, --input="-"  Docker image archive to process, defaults to stdin. Use - to explicitly set stdin

Commands:
  help [<command>...]
    Show help.


  client [<flags>]
    Populate IPFS node with image data

    -r, --registry="http://localhost:5000"
                     Registry to use when generating pull URL
    -n, --no-add     Don`t add to IPFS, just print directory
    -q, --quiet      Produce less output - just the final pullable image name
    -d, --debug      Leave workdir intact (useful for debugging)
    -i, --input="-"  Docker image archive to process, defaults to stdin. Use - to explicitly set stdin

  server [<flags>]
    Run an IFPS-backed registry

    --gateway="http://127.0.0.1:8080"
                    IPFS gateway. It must be reachable by pulling clients
    --addr=":5000"  Listen address.

Configure Docker

As usual add --insecure-registry localhost:5000 to your docker daemon args. You need to put the image2ipfs server behind a reverse proxy if you want to do proper TLS termination.

Demo

Assuming you have completed the steps above, let's publish a centos:7 image to IPFS!

$ docker version
Client:
 Version:      1.11.1
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   5604cbe
 Built:        Tue Apr 26 23:38:55 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.11.1
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   5604cbe-unsupported
 Built:        Sun May  1 20:27:17 2016
 OS/Arch:      linux/amd64

$ image2ipfs --version
0.1.0

$ docker pull centos:7
7: Pulling from library/centos
a02a4930cb5d: Pull complete
Digest: sha256:184e5f35598e333bfa7de10d8fb1cebb5ee4df5bc0f970bf2b1e7c7345136426
Status: Downloaded newer image for centos:7

$ docker save centos:7 | image2ipfs
docker save centos:7 | /home/jvassev/more/gohome/bin/image2ipfs
2019/03/12 15:38:59 workdir is /tmp/image2ipfs887013748
2019/03/12 15:39:01 processing centos:7
2019/03/12 15:39:01 processing layer /tmp/image2ipfs887013748/9841c41d4b0d8fe3bf22b7c3c12e7633870218fffec04d84e7d62993ac175a19/layer.tar
2019/03/12 15:39:11 using ipfs in /home/jvassev/.bin/ipfs
2019/03/12 15:39:12 image ready Qmcsr3naQWG6YzeWTCa7Ee55JUqDdAwvo3j8VyAuRcrHvM
2019/03/12 15:39:12 browse image at http://localhost:8080/ipfs/Qmcsr3naQWG6YzeWTCa7Ee55JUqDdAwvo3j8VyAuRcrHvM
2019/03/12 15:39:12 dockerized hash ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq
2019/03/12 15:39:12 you can pull using localhost:5000/ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq/centos
localhost:5000/ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq/centos

# delete the image to make docker pull layers again
$ docker rmi centos:7

image2ipfs will produce a single line to stdout - the pullable docker image name. The rest is debug output you can suppress with -q.

$ docker pull localhost:5000/ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq/centos
Using default tag: latest
latest: Pulling from ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq/centos
Digest: sha256:4c7a24018edbcb72ec0e6a7ff6809db4aa2f306784bd79ab971e0497954ed4e2
Status: Downloaded newer image for localhost:5000/ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq/centos:latest

$ docker history localhost:5000/ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq/centos
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
778a53015523        4 weeks ago         /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B
<missing>           4 weeks ago         /bin/sh -c #(nop) LABEL name=CentOS Base Imag   0 B
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:6dd89087d4d418ca0c   196.7 MB
<missing>           7 months ago        /bin/sh -c #(nop) MAINTAINER The CentOS Proje   0 B

Depending on how you have started the image2ipfs the docker daemon will be redirected to an IPFS gateway of your choice. By default the registry will redirect to the ipfs daemon running locally (http://localhost:8080).

But what is this "dockerized hash ciqnqalrqxnqzubb2jllburc4e6wt2j7crjx2nxsdfbiw4sceq367lq" in the output? Docker requires image names to be all lowercase which doesn't play nicely with base58-encoded binary. A dockerized IPFS hash is just a base-32 of the same binary value. If you see a string starting with ciq.. that's probably a dockerized IPFS hash.

In automated scenarios you'd probably want to run image2ipfs like this:

# build whatever images
$ docker built -t $TAG ...

# save the resulting image name
$ docker save $TAG | image2ipfs -q -r my-gateway.local:8080 > pull-url.txt

-r my-gateway.local instructs image2ipfs what pull url to produce. In a CI/CD you can distribute this generated url to downstream jobs that need to pull it.

What's next

Not sure. It would be great if an IPFS gateway could speak the Registry v2 protocol at /v2/* so you don't need to run a registry.

The Dockerized hash can be shortened a bit if base-36 is used instead of base-32/hex.

Changelog

  • 0.0.1: Broken, doesn't work
  • 0.0.2: Works only with docker <= 1.9.1
  • 0.0.3: Dockerized hash produced using base-32 (used to be hex/base-16)
  • 0.0.4: Support for schema version 2 and docker > 1.10
  • 0.0.5: image2ipfs: stop handling archives produced by docker < 1.10, ipfs-registry can still work with all post-1.6.x dockers
  • 0.0.6: Deprecate manifest v1. Rewrite image2ipfs-server in golang
  • 0.1.0: Rewrite image2ipfs in go: Single binary for client and server

FAQ

Why can't I use tags or references.

The tag is always "latest". The "real" reference is encoded in the name of the image, that is, the "dockerized hash". Even if you export myimage:my-tag, in the IPFS registry you always tag "latest" but an image like ciq9a3eafb835d79e/myimage:latest. This is very similar to how gx and gx-go work [https://github.com/whyrusleeping/gx].