Developing NEM Docker

## Intro The [nem-docker](https://github.com/rb2nem/nem-docker) is a Docker configuration with accompanying helper scripts published on GitHub. It is one of the easiest solutions to securely deploy and keep a NEM node up to date. This blog post will explain its structure and how it was developed. Running in a container brings benefits like isolation, but it also has some caveats. Some examples are a lack of permanent storage, a specific network setup, and no init system. This config and accompanying scripts help get you up and running with a minimum of fuss, and even follows strict best practice, like checking the hash of the downloaded NEM software (do you do that when installing manually? ;-)). Let's see how it works. ## Two Ways of Running Docker Before running a docker container, you first need to build an image for it. You can see this step as building the filesystem of the container. There are two ways of doing this. You can build the image locally, or you can download an image from [Docker Hub](https://hub.docker.io). ### Pulling the Image from Docker Hub Images are available on dockerhub for running either [nis](https://hub.docker.com/r/rb2nem/nis/) or [ncc](https://hub.docker.com/r/rb2nem/ncc/). The NIS image is actually used by the [Deploy on Azure](https://github.com/NemProject/azure-quickstart-templates/tree/nem-baas-template/docker-nem) scripts. Each live in a specific git branch on GitHub (respectively named [NIS](https://github.com/rb2nem/nem-docker/tree/nis) and [NCC](https://github.com/rb2nem/nem-docker/tree/ncc)). Both branches are based on a third branch named [hub](https://github.com/rb2nem/nem-docker/tree/hub). Most changes take place in the hub branch, and the NIS and NCC branch only hold changes regarding which software needs to be started by default. ### Locally Building You can also build the image locally. The Docker config is held in the [master branch](https://github.com/rb2nem/nem-docker/tree/master) on GitHub. Building the image and running the container off of it is facilitated by the helper scripts is possible. This is actually the best-tested deployment method, and it is recommended that you do it this way. ## Building the image We will focus our attention on the locally built image because the repo holding the Dockerfile also includes sample config files, but differences with the images published on Dockerhub are minimal. For information on how to download the image, see the section below about "Running with Dockerhub images". To run a Docker container, you first need to describe the image it will run off. This is done in the file name [Dockerfile](https://github.com/rb2nem/nem-docker/blob/master/Dockerfile). Let's look at the nem-docker Dockerfile, with links pointing to the relevant lines of the file on GitHub. The container image is [based on Fedora](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L2), and the required [packages are installed](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L4). The [latest release is downloaded](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L8), and its [validity is checked](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L9). If the check succeeds, the archive is decompressed, and the NEM software will be available in the directory `/package`in the container. It then proceeds by [adding a nem user](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L12) and creating some directories where the NEM software data will be stored. The [servant is also downloaded and installed](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L18), making this config suitable to run a supernode. Docker is made to run one command per container. To work around this, which is needed if you want to run a supernode, the command run in our container is [supervisord](http://supervisord.org/), which will control what software to run in the container. When the image is built, a [default supervisord config](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/custom-configs/supervisord.conf.sample) is [is placed in the image](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L23). This config simply defines the available services (NIS, NCC, servant) without starting any automatically. Following this, the Dockerfile [announces which ports the container will listen on](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L24) and then indicates [which command will be run by the container](https://github.com/rb2nem/nem-docker/blob/bd03b889b8f241e88287aa96532002b55849fbc5/Dockerfile#L32). ## Running the container Now that we have built the image, we can run the container with the command `sudo docker run`. However, there are some important things you need to know first. ### Persistent Storage All changes done on the filesystem of the container are specific to that container. When you want to upgrade to the latest NEM version, you will build a new image, and start a new different container, which will not have the changes specific to the old container. When running NIS, that would mean re-downloading the whole blockchain every upgrade. The solution to this is to use [docker volumes](https://docs.docker.com/engine/tutorials/dockervolumes/). The best way to use this is to mount a directory from the host to the container. This lets you inspect the content of the directory as the container runs. This is simply done by using the flags `-v /path/to/host/dir:/home/nem/nem`, as the NEM software data is saved in the container in `/home/nem/nem`. ### Custom configs You certainly want to have custom configs passed to the NEM software running in the container. This is easily done because similarly to mounting a directory in the container, we can also mount an individual file in the container. It is just a matter of mounting the right config file at the right location in the container. Here are the config file locations in the container: * NIS: /package/nis/config-user.properties * NCC: /package/ncc/config-user.properties * Servant: /servant/config.properties * Supervisord: /etc/supervisord.conf If you have a custom config located at `/home/tom/nis.xml`, you can use it by passing the option `-v /home/tom/nis.xml:/package/nis/config-user.properties`. ### Port Mapping Software running in the container will open TCP ports. Mapping these container ports to the host’s ports is done by the `-p container_port:host_port` flag passed to `docker run`. For example, to map the NIS port, pass the option `-p 7890:7890` to docker run. ## Application Let's now build our image and run a Docker container with NIS. First, let's clone the repo: git clone https://github.com/rb2nem/nem-docker.git then build the image: cd nem-docker/ sudo docker build -t nem_image . After some time you should see the last line starting with Successfully built We will run NIS in the container, and we want it to start automatically when we run the container. For that we need to update the supervisord config: cp custom-configs/supervisord.conf.sample custom-configs/supervisord.conf edit custom-configs/supervisord.conf This last line opens the file in vim on my system, but you may replace the `edit` command by your preferred editor. For beginners, `nano` is very simple to use. In the section `[program:nis]` of that file, change the `autostart` line to `autostart=true` and save the file. For demonstration purpose, I will store the NEM data in `/tmp/nem` (**do not do this for your production setup**, choose a base directory other than `/tmp`**or you'll lose those files** at your next reboot...). Create that directory... mkdir /tmp/nem All is now setup for running the container. To run NIS you need to publish port 7890 with `-p 7890:7890`, mount the NEM data directory from the host with `-v "/tmp/nem:/home/nem/nem"`, and mount the custom supervisord config with `-v "$PWD/custom-configs/supervisord.conf:/etc/supervisord.conf"`. Note that mounting files and directories in a container requires passing absolute directories, hence the use of `$PWD`. which is the current working directory. Lastly, name the container `nem`. The full command is: sudo docker run -d -p 7890:7890 \ -v "/tmp/nem:/home/nem/nem" \ -v "$PWD/custom-configs/supervisord.conf:/etc/supervisord.conf" \ --name nem \ nem_image `-d ` runs the container as a daemon, giving you the shell command prompt back. The container will change the owner of the `nem` directory to the `nem` user in the container, which has user id 1000, and it will give read, write and execute to the group of said directory. This should let go in the directory even if your user id is not 1000 on the host. After a couple of seconds you should be able to request the running NIS on port 7890 of your localhost with this command (install curl on your host if needed, or use a web browser): curl http://localhost:7890/status If you receive an error like this one: curl: (56) Recv failure: Connection reset by peer If is probably because NIS is still starting up. When NIS is up, you get this response: {"code":5,"type":4,"message":"status"} Code 5 means `The local node is booted (implies NIS is running).`. You can check [the meaning of other codes](http://bob.nem.ninja/docs/#status-request). If you don’t get a successful answer even after a couple of minutes, you can check the NIS logs in the file `/tmp/nem/nis-stderr.log`. When NIS is runnig, `curl http://localhost:7890/node/info`gives you info about your node. You can now safely stop and remove the container: sudo docker stop nem sudo docker rm nem Because no data is stored in the container, it can be safely removed. Next time you want to run it you issue the exact same `sudo docker run` command, and it will start nearly immediately as it uses cached data to create the container and use the blockchain it has already downloaded. ## Running with Docker Hub Images Here are the commands to do the same as above using images published on Dockerhub rather than building your own. As the rb2nem/nis image is configured to start NIS automatically, you don't have to change the supervisord config: mkdir /tmp/nem sudo docker run -d -p 7890:7890\ -v "/tmp/nem:/home/nem/nem" \ --name nem \ rb2nem/nis However, you still might want to override the default config files used in the container. To get the sample config file, just check out the repo and look under the `custom-configs`directory: git clone https://github.com/rb2nem/nem-docker.git Remember that in those sample configs, no service is autostarted. Don't forget to change the `autostart` field! ## An Easier Way? We've seen how we can run NEM in a Docker container, but it requires running cumbersome commands. Couldn't we make it easier to use by using a script passing all these parameters as needed? Of course we can! The good news is that those scripts exist. We'll take a look at them in the next post covering NEM in a docker container. *The NEM Team would like to thank RB2 for contributing this blog.*
2 Likes

German Translation:

NEM Docker Entwicklung

Einleitung

nem-docker ist eine Docker Konfiguration mit begleitenden Hilfescripts, die auf Github verfügbar sind. Es ist eine der einfachsten Lösungen um einen NEM node einzurichten und aktuell zu halten. Dieser Blogbeitrag erklärt die Struktur und Entwicklung dieser Docker Konfiguration.

Eine Anwendung in einem Container laufen zu lassen bringt Vorteile wie beispielsweise Isolation, es bringt aber auch einige Einschränkungen mitsich. Beispiele hierfür sind: Mangel an permanentem Speicher, ein bestimmtes Netzwerk-Setup und kein init-System. Diese Docker-Konfiguration und die dazugehörigen Hilfescripts helfen dir das Ganze möglichst reibungslos zum laufen zu bringen. Wir befolgen dabei sogar Best Pratice Empfelungen wie die Überprüfung der Hashwerte der huntergeladenen NEM-Software (würdest du das bei einer manuellen Installation auch machen? ;-)) Lass uns loslegen!

Zwei Arten um Docker laufen zu lassen

Bevor ein Docker Container laufen kann, muss zunächst ein Image dafür erstellt werden. Du kannst diesen Schritt mit dem Aufbau eines Dateisystems für den Container vergleichen. Es gibt zwei Möglichkeiten dies zu bewerkstelligen. Entweder erstellst du das Image lokal oder du lädst das Image vom Docker Hub herunter.

Image von Docker Hub herunterladen

Auf Docker Hub sind Images für den Betrieb von nis oder ncc verfügbar.

Das NIS Image wird aktuell vom Deploy on Azure Scripts verwendet.

Jedes Image ist in einem spezifischen Git-Zweig auf Github abgelegt (nis and ncc). Beide Zweige basieren auf einem dritten Zweig der als hub benannt ist. Die meisten Änderungen finden im hub-Zweig statt. Die nis- und ncc-Zweige enthalten nur Änderungen betreffend Software die zuerst gestartet werden muss.

Image lokal erstellen

Du kannst das image auch lokal erstellen. Die Docker-Konfiguration ist im master branch auf GithHub verfügbar.

Das erstellen des Images und und der Betrieb des Containers wird durch die Helfer-Scripts vereinfacht. Dies ist aktuell ist am besten getestete Installationsmethode und es wird empfohlen es auf diese Weise durchzuführen.

Erstellen des Images

Wir konzentrieren uns nun auf das lokal erstellte Image, weil das Reposistory, welches das Dockerfile enthält, auch Beispielkonfigurationsdateien enthält, die aber minimale Unterschiede zu denen auf Dockerhub aufweisen. Weitere Informationen zum Download des Docker-Images findest du weiter unten in diesem Beitrag.

Um einen Docker-Container laufen zu lassen, musst du zuerst das Image beschreiben von dem es nachher laufen soll. Dies wird in der Dockerfile Datei definiert.

Lass uns einen Blick in das nem-docker Dockerfile werfen. Die Links zeigen jeweils auf die relevanten Codezeilen in der Github Datei. Das Container-Image basiert auf Fedora und installiert die benötigten Pakete.

Die aktuellste Version wird heruntergeladen und dessen Valididtät wird überprüft. Wenn die Prüfung erfolgreich ist, wird das Archiv dekomprimiert und die NEM Software wird im /package Verzeichnis des Containers verfügbar.

Anschliessend wird ein NEM user hinzugefügt und weitere Verzeichnisse erstellt, welches NEM Softwaredaten enthalten.

Der Servant wird ebenfalls heruntergealden und installiert, wodurch diese Konfiguration Supernode-tauglich wird.

Docker wurde gemacht um einen Befehl pro Container ausführen zu können. Um diesem Problem aus dem Weg zu gehen, wenn du ein Supernode betreiben möchtest, die Command, die wir verwenden in unserem Container ist supervisord, die kontrolliert welche Software im Container ausgeführt wird.

Wenn ein Image erstellt wird, wird eine Standard supervisord Konfiguration in das Image eingefügt. Diese Konfiguration definiert lediglich welche Services (NIS, NCC, servant) verfügbar sind ohne einen von diesen automatisch zu starten.

Im Weiteren definiert das Dockerfile auf welche Ports der Container hören wird und zeigt anschliessend welche Kommandozeile der Container ausführen wird.

Container ausführen

Jetzt wo das Image erstellt wurde, können wir den Container mit dem befehl sudo docker run ausführen. Nun gibt es noch einige wichtige Dinge die du beachten solltest.

Persistenter Speicher

Alle Änderungen die im Dateisystem des Containers durchgeführt werden beziehen ausschliesslich auf diesen Container.Wenn du ein Upgrade auf die neuste NEM Version durchführen willst, musst du ein neues Image erstellen und einen neuen Container starten, welcher die zurvor durchgeführten Änderungen am alten Container nicht mehr enthält. Wenn du ein NIS am laufen hast, bedeutet das, dass du nach jedem Update die gesamte Blockchain neu herunterladen musst.

Dieses Problem kann mit der Nutzung eines Docker Volumes gelöst werden. Der beste Weg um das zu Nutzen ist, wenn du das Hostverzeichnis direkt an den Container hängst. So kannst du den Inhalt des Verzeichnisses durchsuchen während der Container am laufen ist. Dies kann einfach durch die nutzung des Flags -v /path/to/host/dir:/home/nem/nem bewerkstelligt werden, da die NEM Software Daten innerhalb des Containers in /home/nem/nem gespeichert werden.

Individuelle Konfigurationen

Du möchtest gewiss auch individuelle Konfigurationen an die NEM Software im Container übergeben. Dies ist ebenfalls einfach, da gleich wie beim Einhängen von Verzeichnissen, auch individuelle Dateien an einen Container gehängt werden können. Es ist nur eine Frage des Einhängens des richtigen Files an der richtigen Stelle im Container. Hier findest du die Konfigurationsdateien im Container:

  • NIS: /package/nis/config-user.properties
  • NCC: /package/ncc/config-user.properties
  • Servant: /servant/config.properties
  • Supervisord: /etc/supervisord.conf

Wenn deine individuelle Konfigruation im Verzeichnis /home/tom/nis.xml liegt, kannst du diese mit der Übergabe der folgenden Option nutzen: -v /home/tom/nis.xml:/package/nis/config-user.properties.

Ports zuweisen

Software, die im Container läuft, öffnet TCP ports. Um diese Container Ports den Host Ports zuzuweisen kann folgende Flag -p container_port:host_port dem drocker run übergeben werden.
Zum Beispiel um den NIS port zuzuweisen, übergebe dem docker run die Option -p 7890:7890

Anwendung

Nun lasst uns unser eigenes Image erstellen und einen Docker Container mit NIS laufen lassen.

Zunächst klonen wir das Repository:

git clone https://github.com/rb2nem/nem-docker.git

danach erstellen wir das Image:

cd nem-docker/
sudo docker build -t nem_image

Nach einiger Zeit sollte folgende Meldung erscheinen:

Successfully built

Wir werden NIS im Container laufen lassen und wir wollen NIS automatisch starten lassen, sobald wir den Container am laufen haben. Hierfür müssen wir die supervisord Konfiguration updaten:

cp custom-configs/supervisord.conf.sample custom-configs/supervisord.conf
edit custom-configs/supervisord.conf

Die letzte Zeile öffent die Datei im Editor vim in meinem System, aber du kannst den edit Befehl durch deinen bevorzugten Editor ersetzen. Für Anfänger empfiehlt sich der Einsatz von nano.

Ändere in der Sektion [program:nis] die autostart Zeile zu autostart=true und speichere die Datei.

Für Demonstrationszwecke werde ich die NEM-Daten in /tmp/nem speichern (mach dies bitte nicht in einer produktiven Umgebung, wähle ein anderes Verzeichnis als /tmp sonst wirst du alle Daten verlieren beim nächsten Neustart…). Erstelle folgendes Verzeichnis:

mkdir /tmp/nem

Nun ist alles eingerichtet um den Container laufen zu lassen. Um NIS laufen zu lassen musst du Port 7890 öffnen mit der Option -p 7890:7890, hänge das NEM Datenverzeichnis vom Host mit dem Befehl -v "/tmp/nem:/home/nem/nem" ein, und hänge die supervisord Konfiguration mit -v "$PWD/custom-configs/supervisord.conf:/etc/supervisord.conf" ein.

Beachte, dass eingehängte Dateien und Verzeichnisse in einem Container eine absolute Pfadangabe benötigen, daher wird $PWD verwendet, welches das aktuelle Arbeitsverzeichnis ausgibt. Zuletzt gib dem Container den Namen nem. Der Vollständige Befehl lautet:

sudo docker run -d -p 7890:7890 \
-v "/tmp/nem:/home/nem/nem" \
-v "$PWD/custom-configs/supervisord.conf:/etc/supervisord.conf" \
--name nem \
nem_image

-d lässt den Container als Daemon laufen, welcher dir die Shell-Command zurückgibt.

Der Container wird dem nem Verzeichniss den Benutzer nem zuordnen, welcher die User-ID 1000 hat. Zusätzlich wird der Container die Rechte read (lesen), write (schreiben) und execute (ausführen) der Gruppe des genannten Verzeichnisses zuweisen. Dies sollte den Zugang zum Verzeichnis ermöglichen, auch wenn die User-ID des Hosts nicht 1000 entspricht.

Nach einpaar Sekunden solltest du den laufenden NIS auf Port 7890 auf deinem Locallost mit diesem Befehl erreichen können (installiere curl auf deinem Host, wenn nötig oder nutze einen Webbrowser):

curl http://localhost:7890/status

Falls du folgende Fehlermeldung erhältst:
curl: (56) Recv failure: Connection reset by peer
könnte das NIS möglicherweise noch am starten sein. Wenn NIS aufgestartet ist, solltest du folgende Rückmeldung erhalten:
{"code":5,"type":4,"message":"status"}

Code 5 beudetet:
Der lokale Node ist geladen (impliziert, dass NIS am laufen ist). Hier ist die Bedeutung der anderen Codes beschrieben.

Wenn du selbst nach einigen Minuten keine Erfolgsmeldung erhältst, kannst du den NIS Log unter /tmp/nem/nis-stderr.log überprüfen.

Wenn NIS am laufen ist, gibt dir curl http://localhost:7890/node/info Informationen zu deiner Node.

Nun kannst du den Container sicher stoppen und entfernen:

sudo docker stop nem
sudo docker rm nem

Da keine Daten im Container gespeichert sind, kann er sicher entfernt werden. Nächstes Mal wenn du den Container starten möchtest, gibst du den selben sudo docker run Befehl ein und er wird sofort starten, da er die zwischengespeicherten Daten nutzen kann um den Container zu erstellen. Zusätzlich wird er die bereits heruntergeladene Blockchain nutzen.

Betrieb mit Docker Hub Images

Die folgenden Befehle kannst du nutzen, um mit Docker Hub Images anstatt mit einem selbsterstellten Image zu arbeiten, um das selbe umzusetzen, wie im vorherigen Abschnitt beschrieben.

Das rb2nem/nis Image ist bereits so konfiguriert, dass NIS automatisch gestartet wird. Demzufolge musst du die supervisord Konfiguration nicht ändern:

mkdir /tmp/nem
sudo docker run -d -p 7890:7890\
-v "/tmp/nem:/home/nem/nem" \
--name nem \
rb2nem/nis

Falls du trotzdem die Standard-Konfigurationsdateien die im Container genutzt werden ändern möchtest, nutze die Beispiel-Konfigurationsdatei, die du im Repository im Verzeichnis custom-configs findest:

git clone https://github.com/rb2nem/nem-docker.git

Denke daran, dass in diesen Beispielkonfigurationen kein Service automatisch gestartet wird. Vergesse daher nicht das autostart Feld zu ändern!

Gibt es einen einfacheren Weg?

Wir haben gesehen wie wir NEM in einem Docker Container laufen lassen können, leider erfordert dies das Ausführen von umständlichen Befehlen. Wäre es nicht einfacher ein Script zu erstellen, welches all die benötigten Parameter bei Bedarf übergibt? Natürlich! Die Gute nachricht ist, dass Scripts hierfür existieren. Wir werden dieseim nächsten Beitrag über NEM Docker Container behandeln.

2 Likes

Hi I am new,

Wanted to use the docker config ndev. I am using MacOS Sierra. Got the following error when execute ./ndev in the terminal.

ERROR: Named volume "nis:/Users/azlan/nem-dev/nis/nem:rw" is used in service "nemdevnis" but no declaration was found in the volumes section.
Error: No such container: docker_nemdevnis_1

The following is my yaml file:

version: '2'
services:
  nemdevnis:
#    build: "nis"
    image: "rb2nem/nem-dev-guide:nis"
    volumes:
     - ${persistent_location}:/Users/azlan/nem-dev/nis/nem
  nemdevtools:
#    build: "tools"
    image: "rb2nem/nem-dev-guide:tools"
#    image: "test"
    volumes:
     - ${tools_dir}:/Users/azlan/nem-dev/code
    ports:
     - "8081:8081"
     - "8082:8082"
     - "7778:7778"
     - "7890:7890"

Appreciate for your help, thanks in advance.