Yet another container for our deployment
For the new multi-functional search capabilities in our Cloudapps product, we rely on Elasticsearch as a search engine. The production deployment is solely based on Docker containers controlled by Docker Compose. So we were to add a new service to our docker-compose.yml
file. Luckily, there is an official Elasticsearch Docker image available, so we just had to add a new service to our configuration.
But wait, there's a catch
Years ago Elasticsearch removed the option to run as root, which is a good thing from a security perspective. But each decision is always a trade-off, here between security and operations convenience, as you can read in the controversial discussion. Before this change one could simply add es.insecure.allow.root
to the configuration to run Elasticsearch as root. But now the Elasticsearch container runs as the user elasticsearch
with UID 1000
and GID 0
by default.
Basically it means that the deployment needs special preparation. For the Elasticsearch container to be able to write as non-root to its data directories, mounted as Docker volumes, the host directories need correct ownership and priviledges. You could do this manually, but we generally want to automate the deployment as much as possible and rely on our CI/CD pipelines to do the heavy lifting.
Docker Compose depends_on
to the rescue
So, how can we prepare the host directories before the Elasticsearch container starts? The answer is simple: we can use the depends_on
option in Docker Compose] to control the startup order. This allows us to specify dependencies between services, so that one service starts only after another service has started (or finished in our case). With the following configuration snippet we spin up a throw-away container that prepares the host directories for the volumes before the Elasticsearch container starts:
volumes:
elasticsearch-data:
driver: local
driver_opts:
type: none
o: bind
device: /srv/data/elasticsearch
services:
elasticsearch-volume-init:
image: alpine
volumes:
- elasticsearch-data:/tmp/change-ownership
command: chown --recursive 1000:0 /tmp/change-ownership
elasticsearch:
image: elasticsearch:8.12.2
depends_on:
elasticsearch-volume-init:
condition: service_completed_successfully
environment:
- bootstrap.memory_lock=true
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- ELASTIC_PASSWORD=changeit
- xpack.security.enabled=true
ulimits:
memlock:
soft: -1
hard: -1
expose:
- 9200
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
Yaml
First the elasticsearch-volume-init
service is a simple container that changes the ownership of the volume mounted to /tmp/change-ownership
to the user with UID 1000 and GID 0. Then the elasticsearch
service depends on the elasticsearch-volume-init
and will only start after the dependency has completed successfully. The condition: service_completed_successfully
option ensures that the elasticsearch
service will only start if the elasticsearch-volume-init
service has exited with a zero exit code.
That's it! Now the Elasticsearch container can write to its data directories and we can enjoy the new search capabilities in our Cloudapps product. At this point I want to thank Pratik Chaudhari for his clever workaround to use depends_on
for this purpose.