├── README.md └── esy-docker.mk /README.md: -------------------------------------------------------------------------------- 1 | # esy-docker 2 | 3 | **WARNING:** This is a proof of concept! 4 | 5 | A set of make rules to produce docker images for esy projects. 6 | 7 | ## Usage 8 | 9 | A project should have a lockfile generated using a compatible version of esy. 10 | Currently `esy-docker` depends on esy@0.4.3. 11 | 12 | Drop `esy-docker.mk` in your project and create a `Makefile` which includes it: 13 | 14 | include esy-docker.mk 15 | 16 | build: esy-docker-build 17 | shell: esy-docker-shell 18 | 19 | Then you can run 20 | 21 | % make build 22 | 23 | to build the app within the container and 24 | 25 | % make shell 26 | 27 | to run an interactive bash session inside the container. 28 | 29 | Note the container produced by `esy-docker.mk` isn't suitable for deployment as 30 | it contains all the build time dependencies (including an entire OCaml toolchain 31 | which is big). 32 | 33 | You can refer to `.docker/image.app` file which contains image id of the app 34 | builder container and then copy over needed build artifacts from there: 35 | 36 | FROM as builder 37 | 38 | FROM alpine:3.8 39 | 40 | MKDIR /app 41 | WORKDIR /app 42 | COPY --from=builder /app/_esy/default/build/install /app 43 | RUN /app/bin/myapp.exe 44 | 45 | ### Generating `esyd` wrapper 46 | 47 | `esy-docker` can generate `esyd` command wrapper for `esy` which executes 48 | commands inside esy environment inside docker. 49 | 50 | It also mounts `$PWD/.docker/store` as local project store in docker and thus 51 | `./esyd build` will perform an incremental builds. 52 | 53 | Run: 54 | 55 | % make esyd 56 | 57 | which will generate `./esyd` bash script which can be used further with: 58 | 59 | % ./esyd build 60 | % ./esyd x hello 61 | 62 | and so on. 63 | 64 | Note that if you change your project dependencies you need to regenerate `esyd` 65 | script by executing `make esyd` again. 66 | 67 | ## Further Work 68 | 69 | - Configure `esy-docker.mk` so that an app builder image can be tagged: 70 | ``` 71 | % make TAG=app build 72 | ``` 73 | 74 | - Parallelize building dependencies. 75 | 76 | Currently build is generated in such a way that it builds each dependency 77 | separately using separate `RUN` statement in a Dockerfile. This is done so 78 | each dependency build is cached in a separate docker layer. 79 | 80 | Instead we can perform an enitire build in parallel in a single container and 81 | then export builds artifacts from there as `*.tgz`. Then such generated 82 | Dockerfile can import `*.tgz` instead whcih is much faster. 83 | 84 | ## Related work 85 | 86 | This work was done based on: 87 | 88 | - ["Example Dockerfile for static compilation of ReasonML / OCaml projects using esy"][ref1] 89 | by [@mscharley][] 90 | - ["Example project using esy and reason"][ref2] 91 | by [@joprice][] 92 | 93 | [ref1]: https://gist.github.com/mscharley/b9e7e9d4038938a54278a73ea929f5fc 94 | [ref2]: https://github.com/joprice/reason-esy-example 95 | [@joprice]: https://github.com/joprice 96 | [@mscharley]: https://github.com/mscharley 97 | 98 | -------------------------------------------------------------------------------- /esy-docker.mk: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | 3 | define newline 4 | 5 | 6 | endef 7 | 8 | EMIT = echo -e '$(subst $(newline),\n,$(1))' 9 | 10 | define DOCKERFILE_ESY 11 | # start from node image so we can install esy from npm 12 | 13 | FROM node:10.13-alpine as build 14 | 15 | ENV TERM=dumb \ 16 | LD_LIBRARY_PATH=/usr/local/lib:/usr/lib:/lib 17 | 18 | RUN mkdir /esy 19 | WORKDIR /esy 20 | 21 | ENV NPM_CONFIG_PREFIX=/esy 22 | RUN npm install -g --unsafe-perm esy@0.4.3 23 | 24 | # now that we have esy installed we need a proper runtime 25 | 26 | FROM alpine:3.8 as esy 27 | 28 | ENV TERM=dumb \ 29 | LD_LIBRARY_PATH=/usr/local/lib:/usr/lib:/lib 30 | 31 | WORKDIR / 32 | 33 | COPY --from=build /esy /esy 34 | 35 | RUN apk add --no-cache \ 36 | ca-certificates wget \ 37 | bash curl perl-utils \ 38 | git patch gcc g++ musl-dev make m4 39 | 40 | RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub 41 | RUN wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk 42 | RUN apk add --no-cache glibc-2.28-r0.apk 43 | 44 | ENV PATH=/esy/bin:$$PATH 45 | endef 46 | 47 | define DOCKERIGNORE 48 | .git 49 | node_modules 50 | _esy 51 | esyd 52 | endef 53 | 54 | define DOCKERFILE_DEPS 55 | FROM $(1) 56 | RUN mkdir /app 57 | WORKDIR /app 58 | COPY package.json package.json 59 | COPY esy.lock esy.lock 60 | RUN esy fetch 61 | RUN esy true 62 | RUN rm -rf /root/.esy/3/b /root/.esy/3/s 63 | endef 64 | 65 | define DOCKERFILE_APP 66 | FROM $(1) 67 | COPY --from=$(2) /root/.esy/ /root/.esy 68 | RUN mkdir /app 69 | WORKDIR /app 70 | COPY . . 71 | RUN esy fetch 72 | endef 73 | 74 | define ESYD 75 | #!/bin/bash 76 | 77 | APP_IMAGE_ID="$(1)" 78 | 79 | docker run -v "$$PWD/.docker/store:/app/_esy/default/store" -it "$$APP_IMAGE_ID" esy "$$@" 80 | endef 81 | 82 | define USAGE 83 | Welcome to esy-docker! 84 | 85 | This is a set of make rules to produce docker images for esy projects. 86 | 87 | You can execute the following targets: 88 | 89 | esy-docker-build Builds an application 90 | esy-docker-shell Builds an application and executes bash in a container 91 | 92 | To make those targets available create a Makefile withe following lines: 93 | 94 | include /path/to/esy-docker.mk 95 | 96 | endef 97 | export USAGE 98 | 99 | .DEFAULT: print-usage 100 | 101 | print-usage: 102 | @$(call EMIT,$(USAGE)) 103 | 104 | .docker: 105 | @mkdir -p $(@) 106 | 107 | .PHONY: .docker/Dockerfile.esy 108 | .docker/Dockerfile.esy: .docker 109 | @$(call EMIT,$(DOCKERFILE_ESY)) > $(@) 110 | 111 | .PHONY: Dockerfile.deps 112 | .docker/Dockerfile.deps: .docker .docker/image.esy 113 | @$(call EMIT,$(call DOCKERFILE_DEPS,$(shell cat .docker/image.esy))) > $(@) 114 | 115 | .PHONY: Dockerfile.app 116 | .docker/Dockerfile.app: .docker .docker/image.deps 117 | @$(call EMIT,$(call DOCKERFILE_APP,$(shell cat .docker/image.esy),$(shell cat .docker/image.deps))) > $(@) 118 | 119 | .dockerignore: 120 | @$(call EMIT,$(DOCKERIGNORE)) > $(@) 121 | 122 | .docker/image.esy: .docker .dockerignore .docker/Dockerfile.esy 123 | @docker build . -f .docker/Dockerfile.esy --iidfile $(@) 124 | 125 | .docker/image.deps: .docker .dockerignore .docker/Dockerfile.deps 126 | @docker build . -f .docker/Dockerfile.deps --iidfile $(@) 127 | 128 | .docker/image.app: .docker .dockerignore .docker/Dockerfile.app 129 | @docker build . -f .docker/Dockerfile.app --iidfile $(@) 130 | 131 | esyd: .docker/image.app 132 | @$(call EMIT,$(call ESYD,$(shell cat .docker/image.app))) > $(@) 133 | @chmod +x $(@) 134 | 135 | esy-docker-shell-esy: .docker/image.esy 136 | @docker run -it $$(cat .docker/image.esy) /bin/bash 137 | 138 | esy-docker-build: .docker/image.app 139 | @docker run -it $$(cat .docker/image.app) esy build 140 | 141 | esy-docker-shell: .docker/image.app 142 | @docker run -it $$(cat .docker/image.app) /bin/bash 143 | --------------------------------------------------------------------------------