Sombriks Has A Plan



Containers to serve configuration-friendly applications

One nice thing about containers is they kinda standardized how applications should be distributed. You still need to understand the specifics of adopted technology stack, yes, but when it comes to put it to run, just create an image, publish it into some registry and then spin that image somewhere. We already talk about that here and here.

But to get the state of the art, your container image must not be an opaque and hard to get inside coconut, it must offer some flexibility and be somewhat configurable from the outside world.

TL;DR

Just use environment variables and pass them to the container.

Long version

Most modern applications will end up having to deal with some degree of state management, and solutions like queues, databases and event brokers will take place.

Those components are usually external to the application and need configurations to proper serve us.

And even if not mandatory, it is a best practice to make those configurations sensible to the current application environment.

For all those needs, all modern operating systems offers a solution: environment variables.

What is an environment variable?

They are one of the ways that the current shell has to pass runtime information to a child process.

If your application needs external information, it can receive input from user, query the network or the filesystem or check variables from parent process, usually the shell.

What kind of information my app should grab from it?

External information that changes runtime behavior are a great start:

Note also that kind of externalization precedes all that container stuff in decades. To get your configuration externalized is a common practice since the dawn of modern age computing.

How my application can externalize things?

Most languages and frameworks already have neat ways to recover info from external resources:

That kind of development style implies sometimes tha a application profile or application mode exists and there is some specifications about that, like the twelve factor app methodology.

Building configuration-friendly images

To build a configuration-friendly image we start with a configuration-friendly application, as we debated in previous section.

In this example we can see a reasonable configurable application depending on these key environment variables:

# also depends on NODE_ENV
ALG=aes-256-cbc
SECRET=CH4NG3M3CH4NG3M3CH4NG3M3CH4NG3M3
DEBUG=knex:query,knex:bindings,koa:*

A Dockerfile to package this app would be something like this:

FROM node:18-alpine

# some useful description
LABEL name=simple-knex-koa-example \
      description="small koa.js service consuming database using knex.js"

# files needed to proper build and run this.
ADD index.mjs package.json .env.production .env.test /app/

# mind the trailing '/' in app/, it's important!
ADD app/ /app/app/

# switching for our working directory inside de image filesystem
WORKDIR /app/

# environment configuration.
ENV PORT=3000 \
    NODE_ENV=production \
    PG_CONNECTION_URL='please configure database url connection properly'

# informing the port this image will expose to the outside world
EXPOSE $PORT

# install deps and show image folder structure so it can be checked on logs
RUN npm install; echo "some results: "; pwd; ls -la; ls -la app # ; npm run test

# how this app runs
ENTRYPOINT npm start

Important things to note:

The image build command follows:

# from the root directory, which usually has the Dockerfile
docker build -t sombriks/simple-knex-koa:latest .

Running the image

Now, run the application image and replace the variables accordingly:

docker run --name sample-container -p 3000:3000 \
  -e PG_CONNECTION_URL=postgres://username@password@host/database_name \
  -d sombriks/simple-knex-koa:latest

There!

This is how you override your sensitive configuration.

Some orchestration

Container orchestration is one of the modern design patterns for microservices and containerized applications.

It goes one step forward application packaging and starts to define how different applications can collaborate with each other.

Using a docker-compose file, it's possible to not only indicate how to find out a postgresql database, but provide one to team up with our application. See example file bellow:

version: "3.5"
services:
  knex-koa-app:
    # build: .
    image: sombriks/simple-knex-koa
    environment:
      PG_CONNECTION_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db/${POSTGRES_DB:-books}
    ports:
      - "3000:3000"
    expose:
      - 3000
    healthcheck:
      test: [ "CMD", "wget", "-S", "--spider", "http://127.0.0.1:3000/status" ]
      interval: 30s
      timeout: 30s
      retries: 30
    restart: on-failure
    depends_on:
      db:
        condition: service_healthy
  # https://hub.docker.com/_/postgres
  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-books}
      POSTGRES_USER: ${POSTGRES_USER:-postgres}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
    ports:
      - "5432:5432"
    expose:
      - 5432
    healthcheck:
      # https://www.postgresql.org/docs/9.4/app-pg-isready.html
      test: [ "CMD-SHELL", "pg_isready" ]
      interval: 30s
      timeout: 30s
      retries: 30
    restart: on-failure

This also samples a bit about observability, since we have restart and healthcheck operations.

Most orchestration solutions try to offer such commodities so we not only run packaged applications, but make sure they will keep running and answering when the bell rings.

Cool Tools to deal with containers

Intellij Ultimate has a nice plugin to deal with images, containers and registries:

sample-docker-compose-healthy.png

Conclusion

In this example we used a node application, but the same applies to other languages and stacks.

If your application was configurable already then it's a matter of write the packaging stuff for it, publish the image (or build locally) and provision some infrastructure to it.

See you in the nex article, happy hacking!