Running openHAB 2 in Docker

Please see the Docker Installation Page and DockerHub Readme for the most recent instructions.

Update Jan 30, 2018

Upgrades are now handled by the container.

Update Jan 20, 2017

This update is an almost complete rewrite of the tutorial to comply with recent changes to OH and in preparation of moving the tutorial to the official docs.

Update Nov. 12

The latest build as of Nov. 10 #579 reworks the locations of a lot of config files moving them from runtime/karaf/etc and rutime/etc to userdata/etc. You will likely want to let OH recreate the userdata folder to make sure the userdata/etc folder gets fully populated. When you do, you will have to go through any steps you initially did when first setting it up such as adding the Serial connection for zwave and accepting auto-discovered Items once again.

The big change to the instructions below is the volume to mount the config file is no longer needed.

An alternative upgrade path could be (though I’ve not tested it) is to edit the Docker command to not mount userdata, start the container, and copy the contents of /openhab/userdata/etc out of the container into your host so you have all the needed files.

Failing to do either approaches above will result in an OH that will not load complaining of errors loading/parsing files in etc.

This tutorial is less a step-by-step tutorial and more of a set of issues and gotchas you may encounter running openHAB 2’s official Docker image. For installation instructions please see the official openHAB 2 Docker image on DockerHub.

#Why Docker?
Docker is the most popular among a collection of tools that provide containerization. Containerization allows one to run a server in its own isolated environment without the overhead of running a full virtual machine.

There are several reasons one would want to run openHAB in a Docker container. These include:

  • easily test out different versions
  • run multiple instances side by side
  • easily map the OH ports to other ports without modifying configs
  • isolate OH from the rest of your server environment (e.g. configure the container’s networking so the only way it can be accessed is through a reverse proxy)
  • orchestration and automated deployment of OH and related servers

However, this flexibility comes at a cost. For example, because openHAB is running in its own container with only enough installed to run openHAB, the Exec binding is very likely to be useless to you because the container will not have access to the programs and files you need.

About the openHAB installed in the Image

Inside the Docker Image, openHAB is installed to /openhab. The install is a manual installation so all of the files are located here. This is also set as the home directory of the openhab user.

The Image has a very minimal installation of Linux with no services running and just enough installed to allow openHAB to run.

At the time of this writing, the official image uses the latest snapshot version of openHAB 2.

Obtaining the Official image from DockerHub

The Docker Hub has the basic information necessary to acquire and run the Docker image. Please review those instructions before continuing to select the correct image for your machine and download the image.

Create the openhab user

Just because one is running in an isolated container does not mean running as root is recommended. So first create an openhab user configured to be a system user with no home and no shell. This can be done on Ubuntu and Raspbian with the command:

sudo useradd -r -s /sbin/nologin openhab

Add your regular user to the openhab group.

usermod -a -G openhab <user>

Create the openHAB conf, userdata, and addon directories

These directories will be mounted into the running Docker container and are where the configurations and persistence data will be stored. Note that the software running inside a Docker container cannot follow the symbolic links located in a mounted volume. Make sure the openhab user owns these directories.

mkdir /opt/openhab
mkdir /opt/openhab/conf
mkdir /opt/openhab/userdata
mkdir /opt/openhab/addons
chown -R openhab:openhab

Running the Container as a Service Managed by Docker

Services can be run an maintained on a Linux machine one of two ways, using Docker or using the system’s built in service management (e.g. systemd). If using docker to manage the service, run the following command:

docker run \
        --name openhab \
        --net=host \
        -v /etc/localtime:/etc/localtime:ro \
        -v /etc/timezone:/etc/timezone:ro \
        -v /opt/openhab/conf:/openhab/conf \
        -v /opt/openhab/userdata:/openhab/userdata \
        -v /opt/openhab/addons:/openhab/addons\
        -d \
        --user=<uid> \
        --restart=always \
        openhab/openhab:amd64

Where <uid> is the user ID number for the openhab user which you can obtain using the command id openhab and <arch> is the architecture of your system. It is important that ID number is passed in as the ID for the openhab user inside the container will not match the id of the user on your host system and file permissions may be a bit odd (e.g. why does www-data own my openHAB config files?)

See below for an explanation of the fields passed to docker.

Once it successfully runs (it should be listed with a CREATED time that does not include “restarting” when running docker ps.

To stop the service run docker stop openhab.
To restart the service run docker restart openhab
To start the service run docker start openhab

To change the runtime parameters stop the container then execute the long command above with the new parameters.

Additional fields that may be required:

  • -v /opt/openhab/.java:/openhab/.java if using the Nest binding
  • --device=/dev/ttyUSB0 if using a USB dongle such as a zwave controller

Running the container as a service

If running on a Systemd based Linux distro (Ubuntu 16.1 to be specific). The following openhab2.service file will start a new openHAB 2 container every time it starts the service and destroy that container when the service stops. What that means is any data that you want to preserve between restarts of openHAB 2 (e.g. configuration, databases, etc.) must be mounted from your host file system into the container.

Creating a new container on every run greatly simplifies the upgrade and update process. It also ensures that you start with a fresh install every time you run which can avoid some problems.

[Unit]
Description=openHAB 2
Requires=docker.service
After=docker.service

[Service]
Restart=always
ExecStart=/usr/bin/docker run --name=%n --net=host \
  -v /etc/localtime:/etc/localtime:ro \
  -v /etc/timezone:/etc/timezone:ro \
  -v /opt/openhab/conf:/openhab/conf \
  -v /opt/openhab/userdata:/openhab/userdata \
  -v /opt/openhab/addons:/openhab/addons \
  -v /opt/openhab/.java:/openhab/.java \
  --device=/dev/ttyUSB0 \
  --user=<uid> \
  openhab/openhab:<arch>
ExecStop=/usr/bin/docker stop -t 2 %n ; /usr/bin/docker rm -f %n

[Install]
WantedBy=multi-user.target

Where <uid> is the user ID number for the openhab user which you can obtain using the command id openhab and <arch> is the architecture of your system. It is important that ID number is passed in as the ID for the openhab user inside the container will not match the id of the user on your host system and file permissions may be a bit odd (e.g. why does www-data own my openHAB config files?)

Place this openhab2.service file into /etc/systemd/system.

Then run sudo systemctl enable openhab2.service.

Finally run sudo systemctl start openhab2.service to start openHAB running.

Explanation of Arguments Passed to Docker

  • /usr/bin/docker run : create a new container from the passed in Image (last argument)
  • --name=openhab : give the container a human remember able name
  • --net=host : by default Docker will place a container into its own network stack. However, openHAB 2 requires UPnP discovery so this parameter makes the Docker container use the host’s network stack.
  • -v /etc/localtime:/etc/localtime:ro : ties the time of the container to the host’s time, read only so the container cannot change the host’s time
  • -v /etc/timezone:/etc/timezone:ro : ties the timezone of the container to the host’s time zone, read only so the container cannot change the host’s time zone
  • -v /opt/openhab/conf:/openhab/conf : location of the conf folder for openHAB configurations (NOTE: you must create these folders on the host before running the container)
  • -v /opt/openhab/userdata:/openhab/userdata : location for logs, cache, persistence databases, etc.
  • -v /opt/openhab/addons:/openhab/addons : only needed if installing addons unavailable via PaperUI or the Karaf Console
  • -v /opt/openhab/.java:/openhab/.java : needed by the Nest binding (and others?), location of the security token
  • --device=/dev/ttyUSB0 : location of my zwave controller, change and/or add more --device tags to pass all your devices needed by openHAB to the container
  • openhab/openhab:amd64-offline : name of the Docker Image

Updating the Image

Use the following steps to update the docker image and all installed add-ons.

Stop the container:

docker stop openhab

Delete the container:

docker rm openhab

Delete the contents of /opt/openhab/userdata/cache and /opt/openhab/userdata/tmp

rm -rf /opt/openhab/userdata/cache
rm -rf /opt/openhab/userdata/tmp

Pull down the latest image:

docker pull openhab/openhab:<arch>

where <arch> is your architecture.

Restart the container using the full command above.

With this upgrade approach it is best if one has configured OH using the cfg files (including addons.cfg) rather than using PaperUI or Habmin.

There is an alternative to deleting the cache and tmp directories.

Karaf Console

  1. Log into the Karaf console
    ssh openhab@localhost -p 8101
  2. Run bundle:update <id> where <id> is the ID of the addon as listed when you run bundle:list
  3. Repeat step 2 for all bindings

With this approach you can maintain the installation and configuration of OH in PaperUI or Habmin but must update everything individually.

Limitations and Gotchas

  • The Exec binding is likely not going to be very useful. The container has very little installed (e.g. no Python, ping, hping3, etc.) and almost no access to the host’s file system which eliminates most of the benefit from this binding. To get around this run an external service on the host that can receive commands from OH via REST or MQTT.

  • The openHAB 2.0 Network binding has limited use. The Docker Image does not have “ping” installed so the “use system ping” option cannot be set to true, and I could not find a way to give the Docker Container permission to bind to low numbered ports so the dhcp option doesn’t work.

  • The ssh key used to ssh into the Karaf console changes every time you update the container. Consequently you will have to remove and reaccept the known hosts key from where every you ssh into your console to every time you restart the service.

  • I’m sure other bindings that require externally installed programs or libraries to work or access to host system information (e.g. systeminfo Binding) will similarly have limited use.

11 Likes

Added a section to cover how to update the bindings when the Image has been updated.

1 Like

Thanks so much,

I tried this last night but it wouldnt expose any ports. Command below. Any ideas?

I then used a older fork (1.9) and it worked. Id prefer to use OH2.0

docker run --name=openhab --net=host -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v /opt/openhab/conf:/home/kodi/docker/openhab/conf -v /opt/openhab/userdata:/home/kodi/docker/openhab/userdata -v /opt/openhab/addons:/home/kodi/docker/openhab/addons -d -p 8081 openhab/openhab:amd64-offline

By using the -p 8081 you are overriding the default port exposures. I’m no expert but I believe if you use --net=host not only does it allow UPnP discovery but it also eliminates the need to explicitly expose the network ports using -P or -p.

So if you want/need UPnP then don’t use -p and if you need the ports to be different you need to modify the OH configs in the container to use a different port.

If you don’t want/need UPnP then don’t use --net=host and use the -p options to map all the ports OH uses to the host. To do this though you need to provide both ports to the -p option.

-p <host port>:<container port>

So, to bind OH’s port 8080 to port 8081 on the host you would use

-p 8081:8080

Don’t forget to supply a -p option for 8101 also so you can get to the Karaf console.

Thankyou. Will try tonight.

Whats the difference between the offline and online images?

Offline comes with all the add-ons so when you install an add-on it doesn’t have to download anything. Online downloads the addon’s files when you choose to install it. If your OH server is reliably on the Internet and you don’t mind waiting for it to download to install, or if disk space is a concern, choose online.

Great- thankyou!

I can’t get the latest build in offline or online to expose any ports and load at all. Im going to build the image from the Dockerfile tonight.

Not sure what could be wrong. Are you sure that OH is actually running inside the container? How long do you wait before trying to access it after a restart? Is anything being printed to the logs?

So, it was user error all along.

I tested passing -e OPENHAB_HTTP_PORT=‘8081’ to the container and it didnt help
not sure why thats not respected.

Then I realised I had another server running on 8080. So i changed that to 8081 and then openhab starts perfectly now!!

Yay.

Ok, now i can begin my journey into milights, amazon echo dot and harmony hub fun.

OH 2 discovered everything in my apartments exept the echo dots. This is amazing!!!

Let the good times roll and thanks again Rich!!

1 Like

So just a few heads up here.

RUN adduser --disabled-password --gecos '' --home ${APPDIR} openhab &&\

to

RUN adduser -u 1001 --disabled-password --gecos '' --home ${APPDIR} openhab &&\
docker build -t openhab/openhab .
  • I created a user on my host called openhab with
sudo useradd -u 1001 openhab
  • I had to make the openhab user the owner of the ‘openhab’ dir on my host
sudo chown -R openhab:openhab openhab
  • I started up docker without any config volumes shared:
docker run --name=openhab --net=host -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro --restart always -d openhab/openhab:amd64-offline
  • Then I copied the docker contents to my host with
docker cp 07860dca4899:/openhab /home/kodi/docker/openhab
  • Finally I stopped/removed the docker container and started another one that mapped the containers volumes to my host
docker run --name=openhab --net=host -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro -v /home/kodi/docker/openhab/conf:/openhab/conf -v /home/kodi/docker/openhab/userdata:/openhab/userdata -v /home/kodi/docker/openhab/addons:/openhab/addons --restart always -d openhab/openhab

Now I have a perfect and persistent openhab running.

Hope this helps someone.

The complete file is below.

# openhab image 
FROM multiarch/ubuntu-debootstrap:amd64-wily
#FROM multiarch/ubuntu-debootstrap:armhf-wily   # arch=armhf
#FROM multiarch/ubuntu-debootstrap:arm64-wily   # arch=arm64
ARG ARCH=amd64

ARG DOWNLOAD_URL="https://openhab.ci.cloudbees.com/job/openHAB-Distribution/lastSuccessfulBuild/artifact/distributions/openhab-online/target/openhab-online-2.0.0-SNAPSHOT.zip"
ENV APPDIR="/openhab" OPENHAB_HTTP_PORT='8080' OPENHAB_HTTPS_PORT='8443' EXTRA_JAVA_OPTS=''

# Basic build-time metadata as defined at http://label-schema.org
ARG BUILD_DATE
ARG VCS_REF
LABEL org.label-schema.build-date=$BUILD_DATE \
    org.label-schema.docker.dockerfile="/Dockerfile" \
    org.label-schema.license="EPL" \
    org.label-schema.name="openHAB" \
    org.label-schema.url="http://www.openhab.com/" \
    org.label-schema.vcs-ref=$VCS_REF \
    org.label-schema.vcs-type="Git" \
    org.label-schema.vcs-url="https://github.com/openhab/openhab-docker.git"

# Install Basepackages
RUN \
    apt-get update && \
    apt-get install --no-install-recommends -y \
      software-properties-common \
      sudo \
      unzip \
      wget \
      vim \
    && rm -rf /var/lib/apt/lists/*

# Install Oracle Java
RUN \
  echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
  add-apt-repository -y ppa:webupd8team/java && \
  apt-get update && \
  apt-get install --no-install-recommends -y oracle-java8-installer && \
  rm -rf /var/lib/apt/lists/* && \
  rm -rf /var/cache/oracle-jdk8-installer
ENV JAVA_HOME /usr/lib/jvm/java-8-oracle

# Add openhab user & handle possible device groups for different host systems
# Container base image puts dialout on group id 20, uucp on id 10
# GPIO Group for RPI access
# useradd -u 1001 USERNAME
RUN adduser -u 1001 --disabled-password --gecos '' --home ${APPDIR} openhab &&\
    adduser openhab sudo &&\
    groupadd -g 14 uucp2 &&\
    groupadd -g 16 dialout2 &&\
    groupadd -g 18 dialout3 &&\
    groupadd -g 32 uucp3 &&\
    groupadd -g 997 gpio &&\
    adduser openhab dialout &&\
    adduser openhab uucp &&\
    adduser openhab uucp2 &&\
    adduser openhab dialout2 &&\
    adduser openhab dialout3 &&\
    adduser openhab uucp3 &&\
    adduser openhab gpio &&\

    echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/openhab

WORKDIR ${APPDIR}

RUN \
    wget -nv -O /tmp/openhab.zip ${DOWNLOAD_URL} &&\
    unzip -q /tmp/openhab.zip -d ${APPDIR} &&\
    rm /tmp/openhab.zip

RUN mkdir -p ${APPDIR}/userdata/logs && touch ${APPDIR}/userdata/logs/openhab.log

# Copy directories for host volumes
RUN cp -a /openhab/userdata /openhab/userdata.dist && \
    cp -a /openhab/conf /openhab/conf.dist
COPY files/entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
RUN chmod +x entrypoint.sh

RUN chown -R openhab:openhab ${APPDIR}

USER openhab
# Expose volume with configuration and userdata dir
VOLUME ${APPDIR}/conf ${APPDIR}/userdata ${APPDIR}/addons
EXPOSE 8080 8443 5555 1900
CMD ["server"]
2 Likes

Why port 1900?

port 1900 is the Amazon Echo Discover port.

1 Like

So the Echo can’t discover OH even though the container is set to --net=host? The more I’m seeing on this forum to more I wonder if I really understand what --net=host is doing. My assumption was that basically made it use the host’s network stack which is why UPnP works and why you don’t have to supply -ps for 8080 and 8118.

–net=host allowd the container to utilise the host network stack.

iptables rules can be used inside the container or the host to redirect ports or you can expose the ports inside of the Dockerfile like I did. Both are valid and have the same result.

As far as I know this limits having multiple containers exposing the same port from running in --net=host mode.

For instance, port 80 can only run in one container and any subsequent containers will have that service crash unless you use iptables to grab another port and redirect it to the second container.

–net=host provides available ports on a first come, first serve basis.

So if I understand correctly, you had to supply the -p 1900 because it isn’t supplied as an exported port in the Dockerfile?

I’m just trying to figure out why you had to supply the -p 1900 but not 8080 and 8101 when using --net=host.

My assumption was that whatever ports are are listened on in the container, whether or not they have been exported, get exposed when using --net=host.

I was aware of the issues with having multiple containers running with --net=host and port conflicts.

I edited my original post and removed the “-p 1900/1900udp” as it was a typo. Its not needed if you expose 1900/udp in the Dockerfile before you build thus exposing it via the host.

Sorry for the confusion Rich!!!

1 Like

All files in Github: openhab2-docker

Rich,

I am having a hard time connecting to Karaf. The tutorial says ssh openhab@localhost -p 8081. If I try that from within my LAN (substituting localhost with IP address) it get connection refused. If I exec into my openhab container and try and run it I get ssh not found. In looking at the docker file I don’t see the expose for 8081 either.

Do you know what I am missing here? Also, if I do exec into the container, is there a command I can run to connect to the Karaf console?

thanks,
craig

First, the docs say the ssh port is 8101, not 8081. Are you using the right port?

When you use --net=host you do not need to use EXPOSE nor do you need to use -p to expose the ports from the container. This option makes the container use the host’s network stack rather than a docker container network stack so all servers that bind to a port take that port from the host and appear on the network that way.

If you are not using --net=host, you need to publish the needed ports using the -p option:

-p 8080:8080 -p 8101:8101

An article I recently read says that the EXPOSE option in a Dockerfile should be treated as documentation. You still need to explicitly publish the ports or else Docker assigns the exposed ports to random host ports.

Rich,

Thanks for the quick response. You are correct, I had mistyped the port. It is still not working for me though after fixing.

First, is this the correct location for the source for building the docker container that you are talking about using within this tutorial?

If so, line 82 of the Dockerfile is “EXPOSE 8080 8443 5555”. This seems suspicious as 8101 is not there. Also, I do not see the ssh server being installed to the container via apt-get, and in turn it is not started. Is there something else which is acting like an ssh server inside the container?

When I exec into the running container, and do a ps aux this is what I get:

openhab@77f5b6ed3794:~$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
openhab 1 0.0 0.0 18012 2844 ? Ss 09:43 0:00 /bin/bash -x /entrypoint.sh server
openhab 9 0.0 0.0 4476 1660 ? S 09:43 0:00 /bin/sh /openhab/runtime/bin/karaf
openhab 95 15.1 4.1 5777212 334448 ? Sl 09:43 0:30 /usr/lib/jvm/java-8-oracle/bin/java -Dopenhab.home=/openhab -Dopenhab.conf=/openhab/conf -Dopenhab.runtime=/openhab/runtime -D
openhab 278 2.5 0.0 18224 3228 ? Ss 09:46 0:00 /bin/bash
openhab 289 0.0 0.0 15596 2120 ? R+ 09:46 0:00 ps aux
openhab@77f5b6ed3794:

No mention of the openssh server. So not sure how I would be able to ssh in.

thanks again for your help.