Skip to content

Docker 3 Docker structure

paulhectork edited this page Jul 2, 2026 · 4 revisions

3️⃣ docker and docker-compose structure 📦

Our Docker setup is defined in aikon/front/docker/docker-compose.yml.

Good to know:

  • all paths provided in docker-compose.yml are relative to the directory where the file is located, (aikon/frot/docker/).
  • when several values are defined for a directive (e.g. volumes, env_file) and some enter in conflict, the last one defined will be used.

🛤️ Proxies: x-proxy-setting

In many cases, this section will be useless, but if your organization uses a proxy, this is very critical for your application to work. Docker requests to internet will be proxied using the proxy server, but internal requests (localhost and requests between containers) will not be proxied.

x-proxy-settings: &proxy-settings
  HTTP_PROXY: ${HTTP_PROXY:-}
  HTTPS_PROXY: ${HTTPS_PROXY:-}
  NO_PROXY: "localhost,127.0.0.1,aiiinotate,mirador,cantaloupe,web,mongo,nginx,redis,db,.aikon_network"

For further troubleshooting, you can check the Proxy nightmare section in Troubleshooting.


🌍 web container

The web container contains the Django application and encapsulates several other processes (gunicorn and celery) managed by supervisord.

  web:
    build:
      context: ..                     # we build the image from the parent directory of the current one.
      dockerfile: docker/Dockerfile   # we use the Dockerfile located in the `docker` directory (relative to the context)
      args:                           # arguments passed to the Dockerfile (extracted from .env files in the env_file section)
        USERID: ${USERID}             # allow docker/Dockerfile to use `ARG USERID`
        PORT: ${DJANGO_PORT}          # allow docker/Dockerfile to use `ARG PORT`
    volumes:
      - ${DATA_FOLDER}:/data          # bind the $DATA_FOLDER (absolute path on the host machine) to /data inside the container
      - ../app:/home/aikon/app        # the content of app is copied in Dockerfile but with this line, it will be updated if content is altered in the container
    env_file:
      - ../app/config/.env            # load environment variables from the django .env file
      - ./.env                        # load environment variables from the docker/.env file (⚠️ if any variable is defined in both files, the one defined in the second file will be used)
    environment:
      <<: *proxy-settings             # use the proxy settings defined above
    ports:
      - "${DJANGO_PORT}:8000"         # bind the port defined in the .env file to port 8000 inside the container
    depends_on:                       # the web container will be started only after the db and redis containers are started
      - db
      - redis
    networks:
      - aikon_network                 # the container has access to the aikon_network
    restart: unless-stopped           # the container will restart if the machine reboots

Dockerfile: this file is the set of instructions to build the image of the web container. It contains the following steps:

  1. Compile Svelte code
  2. Base image: the image is based on ubuntu:22.04
  3. Set variables: the USERID and PORT variables are passed as build arguments to the Dockerfile
  4. Install dependencies: install necessary system packages
  5. Copy/Create files and set permissions: create necessary directories and copy content of app/ folder
  6. Install Python deps
  7. Expose port: expose the port on which the application will run
  8. Run: the RUN commands contains
    • `manage.sh: the final configs that require other containers to run
    • supervisord: run the application using supervisord (configured in docker/supervisord.conf)

🐘 db container

The db container contains the PostgreSQL database. It is configured to use a volume to persist data. It uses an official docker image so configuration is easy.

  db:
    image: postgres:14                           # official postgres image (when upgrading, look at #Postgres-version section for instructions)
    command: postgres -p ${DB_PORT:-5432}        # defining this command allows us to use a custom port
    volumes:
      - pgdata:/var/lib/postgresql/data          # bind pgdata volume to /var/lib/postgresql/data inside the container
    env_file:
      - ../app/config/.env                       # to get $POSTGRES_DB, POSTGRES_USER, $POSTGRES_PASSWORD, $DB_PORT
    networks:
      - aikon_network                            # the container has access to the aikon_network
    ports:
      - "${DB_PORT:-5432}:${DB_PORT:-5432}"      # <port_outside_of_container>:<port_inside_of_container> => $DB_PORT or 5432 if not defined
    restart: unless-stopped                      # the container will restart if the machine reboots
    healthcheck:                                 # command that is run to check whether the container runs correctly
      test: [ "CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER} -p $${DB_PORT:-5432}" ]
      interval: 10s
      timeout: 5s
      retries: 5

🍈 cantaloupe container

The cantaloupe container contains the IIIF image server: it serves images to the web application. The image is built from the cantaloupe folder, which contains a Dockerfile and a cantaloupe.properties file.

  cantaloupe:
    build:
      context: ..                                              # The container is built in the front/ directory
      dockerfile: cantaloupe/Dockerfile                        # The Dockerfile is thus located in the front/cantaloupe/ directory
    env_file:
      - ./.env                                                 # Get $CANTALOUPE_PORT
    ports:
      - "${CANTALOUPE_PORT:-8182}:${CANTALOUPE_PORT:-8182}"    # <port_outside_of_container>:<port_inside_of_container> => $CANTALOUPE_PORT or 8182 if not defined
    volumes:
      - ${DATA_FOLDER}:/data                                   # bind the $DATA_FOLDER (path on the host machine) to /data inside the container
    networks:
      - aikon_network                                          # the container has access to the aikon_network
    environment:
      <<: *proxy-settings                                      # use the proxy settings defined above
    user: root                                                 # run the container as root user

Cantaloupe is configured using the cantaloupe.properties file, which is generated by the init.sh script. The cantaloupe.properties tells Cantaloupe 🍈 to use /data/mediafiles/img as the root directory for images. By setting volumes to ${DATA_FOLDER}:/data, Cantaloupe will directly access the images stored in ${DATA_FOLDER}/mediafiles/img folder on the host machine.

The Cantaloupe Dockerfile is the set of instructions to build the image for the cantaloupe container.

It contains the following steps:

  1. Base image: the image is based on openjdk:11-jdk
  2. Working directory: the working directory is set to /cantaloupe
  3. Copy files: the content of cantaloupe/ folder is copied to the working directory
  4. Start script: the start.sh permissions are updated and then is executed

The command inside the start.sh script must be run with sudo or as root user. It launched the Cantaloupe server with the cantaloupe.properties file as argument.


👀 mirador container

Mirador 4 is used to visualize IIIF manifests, view them, and view, create, modify and delete IIIF annotations. Those annotations are stored in an aiiinotate instance.

  mirador:
    build:
      context: ..
      dockerfile: annotations/Dockerfile-mirador  # we create a Mirador Dockerfile ourselvers. It bundles together Mirador 4 and the Mirador-Annotation-Editor plugin.
      args:
        PORT: ${MIRADOR_PORT}                     # port Mirador works on
        ENV_PATH: ../app/config/.env              # our config file
    env_file:
      - ../app/config/.env
    depends_on:
      - aiiinotate                                # is dependent on aiiinotate to store annos
    ports:
      - "${MIRADOR_PORT}:${MIRADOR_PORT}"         # external and internal Docker ports are the same.
    networks:
      - aikon_network
    environment:
      <<: *proxy-settings
    restart: always

The Dockerfile-mirador contains the following steps:

  1. Pull a NodeJS image
  2. Setup environment (read arguments, copy .env file ocally)
  3. Pull system packages
  4. Setup your NPM project (create it and install packages, mostly Mirador and Mirador-annotation-editor)
  5. Chmod run script so that is can be executed
  6. Run: this will serve an HTML file with JS that bundles together a Mirador instance with MAE connected to the Dockerized aiiinotate instance.

🖼️ aiiinotate container

aiiinotate is a IIIF annotation server used to store results of Region extractions as IIIF annotations. It relies on the mongo container.

  aiiinotate:                                         # container name
    build:
      context: ..
      dockerfile: annotations/Dockerfile-aiiinotate   # the dockerfile, stored in `aikon/frot/annotations`
      args:                                           # build arguments
        PORT: ${AIIINOTATE_PORT}                      # the port aiiinotate runs on
        ENV_PATH: ../app/config/.env                  # path to our .env
    env_file:
      - ../app/config/.env                            # path to our .env
    ports:
      - "${AIIINOTATE_PORT}:${AIIINOTATE_PORT}"       # port aiiinotate will run on
    extra_hosts:
      # fixes aiiinotate-to-web DNS resolution failures: adds an /etc/hosts entry
      # that maps "${PROD_URL}" to the host machine's Docker bridge IP (host-gateway),
      # bypassing Docker's internal DNS entirely. Requests then reach NGINX directly
      # without a DNS lookup.
      - "${PROD_URL}:host-gateway"
    depends_on:
      mongo:                                          # mongo DB where annos. are stored
        condition: service_healthy                    # healthcheck
    networks:
      - aikon_network
    environment:
      <<: *proxy-settings
    restart: always

The Dockerfile-aiiinotate runs the following steps:

  1. Pull a NodeJS image
  2. Setup environment (read arguments, copy .env file ocally)
  3. Pull system packages
  4. Setup your NPM project (create it and install aiiinotate in int)
  5. Expose port
  6. Run: create the daabase and start the aiiinotate instance

🧠 mongo container

MongoDB is aiiinotate's database backend. It stores IIIF annotations. It uses an official docker image so configuration is easy.

  mongo:
    image: mongo:8            # official mongo image
    restart: always
    networks:
      - aikon_network
    volumes:
      - mongodata:/data/db    # shared volume for data persistency
    environment:
      <<: *proxy-settings
    healthcheck:              # make sure mongo is healthy
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5

🍪 redis container

The redis container contains the Redis database, which is used as broker for Celery tasks. Data is persisted in redisdata volume. It uses an official docker image so configuration is easy. Note that Redis passwords are not supported.

  redis:
    image: redis:6                                                         # official redis image
    command: redis-server --port ${REDIS_PORT:-6379} --loglevel warning    # defining this command allows us to use a custom port
    volumes:
      - redisdata:/data                                                    # bind redisdata volume to /data inside the container
    env_file:
      - ../app/config/.env                                                 # get $REDIS_PORT and $REDIS_PASSWORD
    networks:
      - aikon_network                                                      # the container has access to the aikon_network
    ports:
      - "${REDIS_PORT:-6379}:${REDIS_PORT:-6379}"                          # <port_outside_of_container>:<port_inside_of_container> => $REDIS_PORT or 6379 if not defined
    restart: unless-stopped                                                # the container will restart if the machine reboots
    healthcheck:                                                           # command that is run to check whether the container runs correctly
      test: [ "CMD", "redis-cli", "-p", "${REDIS_PORT:-6379}", "ping" ]
      interval: 10s
      timeout: 5s
      retries: 5

⛓️ nginx container

  nginx:
    image: nginx:latest
    env_file:
      - ./.env
      - ../app/config/.env
    ports:
      - "${NGINX_PORT:-8080}:${NGINX_PORT:-8080}"
    environment:
      <<: *proxy-settings
      PROD_URL: ${PROD_URL:-localhost}
      NGINX_PORT: ${NGINX_PORT:-8080}
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ../app/staticfiles:/home/aikon/app/staticfiles:ro
      - ${DATA_FOLDER}:/data:ro
      - /etc/ssl:/etc/ssl:ro
    depends_on:
      - web
      - cantaloupe
      - sas
    networks:
      - aikon_network
    restart: unless-stopped

🏋️‍♀️ Volumes and networks

Volumes are used to persist data in between builds and to share data between containers. Networks are used to allow communication between containers: aikon_network is the bridge network that connects all containers together.

volumes:
  pgdata:
  redisdata:
  mongodata:
  staticfiles:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: ../app/staticfiles

networks:
  aikon_network:
    driver: bridge

Clone this wiki locally