3

I am entirely new to the concept of dockers. I am creating the following Dockerfile as an exercise.

FROM ubuntu:latest

MAINTAINER kesarling

RUN apt update && apt upgrade -y
RUN apt install nginx curl zip unzip -y
RUN apt install openjdk-14-jdk python3 python3-doc clang golang-go gcc g++ -y
RUN curl -s "https://get.sdkman.io" | bash
RUN bash /root/.sdkman/bin/sdkman-init.sh
RUN sdk version
RUN yes | bash -c 'sdk install kotlin'

CMD [ "echo","The development environment has now been fully setup with C, C++, JAVA, Python3, Go and Kotlin" ]

I am using SDKMAN! to install Kotlin. The problem initially was that instead of using RUN bash /root/.sdkman/bin/sdkman-init.sh, I was using RUN source /root/.sdkman/bin/sdkman-init.sh. However, it gave the error saying source not found. So, I tried using RUN . /root/.sdkman/bin/sdkman-init.sh, and it did not work. However, RUN bash /root/.sdkman/bin/sdkman-init.sh seems to work, as in does not give any error and tries to run the next command. However, the docker then gives error saying sdk: not found

Where am I going wrong?

It should be noted that these steps worked like charm for my host distribution (The one on which I'm running docker) which is Pop!_OS 20.04

kesarling He-Him
  • 1,944
  • 3
  • 14
  • 39
  • Where is `sdk` installed? (Maybe you should check the value of `PATH`.) – ynn Jun 04 '20 at 07:17
  • Did you do the [configuration](https://stackoverflow.com/a/45571203/8776746) for `sdk`? – ynn Jun 04 '20 at 07:20
  • @ynn, Uh.. you see, I have written the exact steps in the `Dockerfile`, that I did for my host machine(Okay, firstly, is the machine on which I install docker called the host machine, the way we do for VMs?), and it worked fine without any kind of configuration (in my host machine). Is there a special need to configure `sdkman` in `Dockerfile`? – kesarling He-Him Jun 04 '20 at 07:24
  • 2
    (1) Yes. You can call your host system as "host". That is a valid name even in Docker. (2) No. Generally, it is insufficient to mimic the same steps as you do in the host. Please don't forget you are setting up a different OS (`ubuntu` vs `pop!_os`) and, more importantly, a docker base image (e.g. `ubuntu:latest`) is usually minimal; many configurations or essential tools are missing. – ynn Jun 04 '20 at 07:28

3 Answers3

13

Actually the script /root/.sdkman/bin/sdkman-init.sh sources the sdk

source is a built-in to bash rather than a binary somewhere on the filesystem.

source command executes the file in the current shell.

Each RUN instruction will execute any commands in a new layer on top of the current image and commit the results.

The resulting committed image will be used for the next step in the Dockerfile.

Try this:

FROM ubuntu:latest

MAINTAINER kesarling

RUN apt update && apt upgrade -y
RUN apt install nginx curl zip unzip -y
RUN apt install openjdk-14-jdk python3 python3-doc clang golang-go gcc g++ -y
RUN curl -s "https://get.sdkman.io" | bash
RUN /bin/bash -c "source /root/.sdkman/bin/sdkman-init.sh; sdk version; sdk install kotlin"

CMD [ "echo","The development environment has now been fully setup with C, C++, JAVA, Python3, Go and Kotlin" ]

G1Rao
  • 424
  • 5
  • 11
  • I think the phrase "*`SHELL` instruction*" is ambiguous; `bash` is also a (sub-)shell. I understand what you want to say, but it may not be beginner-friendly. – ynn Jun 04 '20 at 07:40
  • 1
    okay, will explain, did you try building docker image with my Dockerfile? – G1Rao Jun 04 '20 at 07:42
  • Thank you. No, I'm sorry I don't compile unknown `Dockerfile`s for [a security reason](https://opensource.com/business/14/7/docker-security-selinux). (I know nothing about `sdk`.) – ynn Jun 04 '20 at 07:46
  • Oh! You mean `RUN bash /root/.sdkman/bin/sdkman-init.sh` and `RUN sdk version` were running in 2 different images? – kesarling He-Him Jun 04 '20 at 07:55
  • yes, absolutely, those are two different layers of an image, ofcourse two different shells – G1Rao Jun 04 '20 at 07:55
  • @ynn, sdk is widely known, in fact as you know we even have a related tag ;) So it is pretty safe to go ahead and install :P – kesarling He-Him Jun 04 '20 at 12:07
8

SDKMAN in Ubuntu Dockerfile

tl;dr

  1. the sdk command is not a binary but a bash script loaded into memory
  2. Shell sessions are a "process", which means environment variables and declared shell function only exist for the duration that shell session exists; which lasts only as long as the RUN command.
  3. Manually tweak your PATH
RUN apt-get update && apt-get install curl bash unzip zip -y
RUN curl -s "https://get.sdkman.io" | bash

RUN source "$HOME/.sdkman/bin/sdkman-init.sh" \
    && sdk install java 8.0.275-amzn \
    && sdk install sbt 1.4.2 \
    && sdk install scala 2.12.12

ENV PATH=/root/.sdkman/candidates/java/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/scala/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/sbt/current/bin:$PATH

Full Version

Oh wow this was a journey to figure out. Below each line is commented as to why certain commands are run.

I learnt a lot about how unix works and how sdkman works and how docker works and why the intersection of the three give very unusual behaviour.

# I am using a multi-stage build so I am just copying the built artifacts
# from this stage to keep final image small.
FROM ubuntu:latest as ScalaBuild

# Switch from `sh -c` to `bash -c` as the shell behind a `RUN` command.
SHELL ["/bin/bash", "-c"]

# Usual updates
RUN apt-get update && apt-get upgrade -y
# Dependencies for sdkman installation
RUN apt-get install curl bash unzip zip -y

#Install sdkman
RUN curl -s "https://get.sdkman.io" | bash

# FUN FACTS:
# 1) the `sdk` command is not a binary but a bash script loaded into memory
# 2) Shell sessions are a "process", which means environment variables
#    and declared shell function only exist for 
#    the duration that shell session exists
RUN source "$HOME/.sdkman/bin/sdkman-init.sh" \
    && sdk install java 8.0.275-amzn \
    && sdk install sbt 1.4.2 \
    && sdk install scala 2.12.12

# Once the real binaries exist these are 
# the symlinked paths that need to exist on PATH
ENV PATH=/root/.sdkman/candidates/java/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/scala/current/bin:$PATH
ENV PATH=/root/.sdkman/candidates/sbt/current/bin:$PATH

# This is specific to running a minimal empty Scala project and packaging it
RUN touch build.sbt
RUN sbt compile
RUN sbt package


FROM alpine AS production

# setup production environment image here

COPY --from=ScalaBuild /root/target/scala-2.12/ $INSTALL_PATH

ENTRYPOINT ["java", "-cp", "$INSTALL_PATH", "your.main.classfile"]
Josh Peak
  • 5,898
  • 4
  • 40
  • 52
1

Generally you want to avoid using "version manager" type tools in Docker; it's better to install a specific version of the compiler or runtime you need.

In the case of Kotlin, it's a JVM application distributed as a zip file so it should be fairly easy to install:

FROM openjdk:15-slim
ARG KOTLIN_VERSION=1.3.72

# Get OS-level updates:
RUN apt-get update \
 && apt-get install --no-install-recommends --assume-yes \
      curl \
      unzip
# and if you need C/Python dependencies, those too

# Download and unpack Kotlin
RUN cd /opt \
 && curl -LO https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip \
 && unzip kotlin-compiler-${KOTLIN_VERSION}.zip \
 && rm kotlin-compiler-${KOTLIN_VERSION}.zip

# Add its directory to $PATH
ENV PATH=/opt/kotlinc/bin:$PATH

The real problem with version managers is that they heavily depend on the tool setting environment variables. As @JeevanRao notes in their answer, each Dockerfile RUN command runs in a separate shell in a separate container, and any environment variable settings within that command get lost for the next command.

# Does absolutely nothing: environment variables do not stay set
RUN . /root/.sdkman/bin/sdkman-init.sh

Since an image generally contains only one application and its runtime, you don't need the ability to change which version of the runtime or compiler you're using. My Dockerfile example passes it as an ARG, so you can change it in the Dockerfile or pass a docker build --build-arg KOTLIN_VERSION=... option to use a different version.

David Maze
  • 130,717
  • 29
  • 175
  • 215
  • Hmm... I will need time to analyze and learn the Dockerfile you've suggested. As I have already stated, this is just my second day into docker. In fact I'm so new that about a week ago, I wasn't even aware that such a thing like Docker existed. But I'll definitely try the answer you've suggested. Thanks a lot :) – kesarling He-Him Jun 04 '20 at 12:04