Tuesday, 19 September 2017

Setting up Oracle XE in Docker - Lessons Learned

For a little side project I'm working on, I had the need to set up a Docker instance of Oracle. I have read about Docker a while back and understand the general concept - but up until now, I hadn't had much experience with it (other that running the basic hello-world Docker example).

So, since I'm about using the free version of the Oracle Database at the moment (Oracle XE), I wanted specifically to build that in a VM. Now, I know that Oracle has a GitHub repository with some sample Docker files - unfortunately, I didn't have success with the Oracle XE version, so it wasn't a bad time to go and build my own.

Here are some things I learnt:

1. The default storage location on my Ubuntu system is: /var/lib/docker

This is a problem as I'm developing, since the way my disk is set up, the root partition soon fills up.

2. You can change the location that images are stored, by creating a json file: /etc/docker/daemon.json.

In here, you will want an entry "data-root" with the path to your desired location. The Docker daemon runs as a service, which is effectively the `dockerd` binary. So, whilst testing your config, it's a good idea to stop that (systemctl stop docker). Then, update your config and run `dockerd`. There is also a `debug` property (true|false) that you can set so you get additional output. The first time I ran the daemon with the config file in place, my system had to set up a storage pool

DEBU[0001] devmapper: Pool doesn't exist. Creating it.

This seemed to bring my system to a hault, but leaving my system and coming back it was doing something - so keep in mind that isn't a quick process!

Something curious is that this config file allows you also to specify the storage driver. Now, I don't know much about the Docker storage drivers at this stage, but without the config file it uses the `aufs` storage driver, however if I try to set that in my config file, I get an error about the driver not being available. So it's possible I'm missing some other corresponding entry.

Once all looks good, switch back to using the daemon as a service: systemctl start docker

3. The CMD argument in your Dockerfile is best as a single executable

The CMD argument is what get executed whenever you run a new instance of your image. For the image I built, my command was to start the oracle-xe service. So I started with CMD ["/etc/init.d/oracle-xe", "start"]. This is good, but the problem is when running in detached mode, because the CMD completes, the container also exits.

After doing a bit of research and looking at the Oracle's example Dockerfile, it is a good idea to watch a file - the Oracle one was monitoring an log file, but I so another example online suggested just to watch /dev/null. So effectively I needed to run two commands:

1. oracle-xe start
2. tail -f /dev/null

When I tried chaining these in the CMD property of the Dockerfile, the container threw errors.

Again, after some more research and looking at the example on Oracle's repository, if you have more than one program to run, you should place this in a simple shell script, and pass that into the CMD property.

So, I end up with a script:

/etc/init.d/oracle-xe start
tail -f /dev/null

Then the Dockerfile referencing:

CMD ["/root/install/runXe.sh"]

4. The images are generally very minimal and need to have context set up

If you run the Ubuntu image:

docker run -it ubuntu:16.04

You will see there is very little environment variables set up:

root@b7535b34011e:/# pwd

So, it's not a bad idea to set up some basic environment such as the user

USER root
ENV HOME /root 

When copying files over, at least now we are placing them within the users home (/root) rather than the root of the file system.

5. It's a good idea to run interactively whilst developing

Docker supports two methods to run an image

-i, --interactive=true|false
    Keep STDIN open even if not attached. The default is false.

-d, --detach=true|false
    Detached mode: run the container in the background and print the new container ID. The default is false.

I like to run interactively most of the time to see everything is behaving as expected and to see any output. So the full command would be something like:

docker run -it <image_tag>

Replace i with d when all looks good!

If your CMD property isn't giving the desired result, you can override what is set in the image by passing the --entrypoint command line argument with the value of /bin/bash

docker run -it --entrypoint=/bin/bash <image_tag>

6. You need to set the shared memory size in the command line argument

XE requires at least 1gb of shared memory, so I am a generous guy and give 2gb. This needs to be set when running the image through an argument. So the previous example becomes:

docker run --shm-size=2g -it --entrypoint=/bin/bash <image_tag>

7. Avoid anything that depends on a hostname

When installing XE, the generated listener.ora will reference the hostname. Since each time you run an image in a new container the hostname will be something different, it's a good idea to update this so that you will be able to connect on each corresponding container.

See the example of the listener.ora from the Oracle repository here: https://github.com/oracle/docker-images/blob/master/OracleDatabase/dockerfiles/

8. Finally, some useful commands

docker build -t   
docker run --shm-size=2g -it 

# Stop running containers
docker stop $(docker ps -q -a)
# Remove all containers
docker rm $(docker ps -a -q)
# Delete images with no tag/name
docker rmi $(docker images -f "dangling=true" -q)
# Delete all images
docker rmi $(docker images -q)