24.6 Building and modifying images

The only other section on this page is: Local development and testing with Docker

For general use, it is sufficient to use the pre-built PEcAn images hosted on Docker Hub (see Docker quickstart). However, there are cases where it makes sense to re-build the Docker images locally. The following is a list of PEcAn-specific images and reasons why you would want to rebuild them locally:

  • pecan/depends – Rebuild if:
    • You modify the docker/depends/Dockerfile
    • You introduce new system dependencies (i.e. things that need to be installed with apt-get)
    • You introduce new R package dependencies, and you want those R package installations to be cached during future builds. For packages with fast build times, it may be fine to let them be installed as part of PEcAn’s standard build process (i.e. make).
  • pecan/base – Rebuild if:
    • You built a new version of pecan/depends (on which pecan/base depends)
    • You modify the docker/base/Dockerfile
    • You made changes to the PEcAn R package source code, the Makefile, or web/workflow.R.
      • NOTE that changes to the web interface code affect pecan/web, not pecan/base
  • pecan/executor – Rebuild if:
    • You built a new version of pecan/base (on which pecan/executor depends) and/or, pecan/depends (on which pecan/base depends)
    • You modified the docker/executor/Dockerfile
    • You modified the RabbitMQ Python script (e.g. docker/receiver.py)
  • pecan/web – Rebuild if you modified any of the following:
    • docker/web/Dockerfile
    • The PHP/HTML/JavaScript code for the PEcAn web interface in web/ (except web/workflow.R – that goes in pecan/base)
    • docker/config.docker.php (the config.php file for Docker web instances)
    • documentation/index_vm.html (the documentation HTML website)
    • NOTE: Because changes to this code are applied instantly (i.e. do not require compilation or installation), a more effective way to do local development may be to mount the web/ or other relevant folders as a volume onto the pecan/web container.

The easiest way to quickly re-build all of the images is using the docker.sh script in the PEcAn source code root directory. This script will build all of the docker images locally on your machine, and tag them as latest. This will not build the pecan/depends image by default because that takes considerably longer. However, you can force the script to build pecan/depends as well by setting the DEPEND environment variable to 1 (i.e. DEPEND=1 ./docker.sh). The following instructions provide details on how to build each image individually.

To build an image locally, use the docker build command as described below. For more details, see docker build --help or the online Docker build documentation.

First, in a terminal window, navigate (cd) into the PEcAn source code root directory. From there, the general syntax for building an image looks like the following:

docker build -t pecan/<image name>:<image version> -f docker/base/Dockerfile.<image name> .

For instance, to build a local version of the pecan/depends:latest image, you would run:

docker build -t pecan/depends:latest -f docker/depends/Dockerfile .

The breakdown of this command is as follows:

  • docker build – This is the core command. The standard syntax is docker build [OPTIONS] <PATH>, where <PATH> refers to the directory to be used as the “build context”. The “build context” is the working directory assumed by the Dockerfiles. In PEcAn, this is always the PEcAn source code root directory, which allows Dockerfiles to use instructions such as COPY web/workflow.R /work/. In this example, the <PATH> is set to the current working directory, i.e. . because we are already in the PEcAn root directory. If you were located in a different directory, you would have to provide a path to the PEcAn source code root directory. Also, by default, docker build will look for a Dockerfile located at <PATH>/Dockerfile, but this is modified by the -f option described below.

  • -t pecan/depends:latest – The -t/--tag option specifies how the image will be labeled. By default, Docker only defines unique image IDs, which are hexidecimal strings that are unintuitive and hard to remember. Tags are useful for referring to specific images in a human-readable way. Note that the same unique image can have multiple tags associated with it, so it is possible for, e.g. pecan/depends:latest, pecan/depends:custom, and even mypecan/somethingelse:20.0 to refer to the same exact image. To see a table of all local images, including their tags and IDs, run docker image ls.

    • NOTE: PEcAn’s docker-compose.yml can be configured via the PECAN environment variable to point at different versions of PEcAn images. By default, it points to the :latest versions of all images. However, if you wanted to, for instance, build :local images corresponding to your local source code and then run that version of PEcAn, you would run:
    PECAN=local docker-compose -p pecan up -d

    This is an effective way to do local development and testing of different PEcAn versions, as described below.

  • -f docker/depends/Dockerfile – The -f/--file tag is used to provide an alternative location and file name for the Dockerfile.

24.6.1 Local development and testing with Docker

The following is an example of one possible workflow for developing and testing PEcAn using local Docker images. The basic idea is to mount a local version of the PEcAn source code onto a running pecan/executor image, and then send a special “rebuild” RabbitMQ message to the container to trigger the rebuild whenever you make changes. NOTE: All commands assume you are working from the PEcAn source code root directory.

  1. In the PEcAn source code directory, create a docker-compose.override.yml file with the following contents.:

    version: "3"
    services:
      executor:
        volumes:
          - .:/pecan

    This will mount the current directory . to the /pecan directory in the executor container. The special docker-compose.override.yml file is read automatically by docker-compose and overrides or extends any instructions set in the original docker-compose.yml file. It provides a convenient way to host server-specific configurations without having to modify the project-wide (and version-controlled) default configuration. For more details, see the Docker Compose documentation.

  2. Update your PEcAn Docker stack with docker-compose up -d. If the stack is already running, this should only restart your executor instance while leaving the remaining containers running.

  3. To update to the latest local code, run ./scripts/docker_rebuild.sh. Under the hood, this uses curl to post a RabbitMQ message to a running Docker instance. By default, the scripts assumes that username and password are both guest and that the RabbitMQ URL is http://localhost:8000/rabbitmq. All of these can be customized by setting the environment variables RABBITMQ_USER, RABBITMQ_PASSWORD, and RABBITMQ_URL, respectively (or running the script prefixed with those variables, e.g. RABBITMQ_USER=carya RABBITMQ_PASSWORD=illinois ./scripts/docker_rebuild.sh). This step can be repeated whenever you want to trigger a rebuild of the local code.

NOTE: The updates with this workflow are specific to the running container session; restarting the executor container will revert to the previous versions of the installed packages. To make persistent changes, you should re-build the pecan/base and pecan/executor containers against the current version of the source code.

NOTE: The mounted PEcAn source code directory includes everything in your local source directory, including installation artifacts used by make. This can lead to two common issues: - Any previous make cache files (stuff in the .install, .docs, etc. directories) persist across container instances, even though the installed packages may not. To ensure a complete build, it’s a good idea to run make clean on the host machine to remove these artifacts. - Similarly, any installation artifacts from local builds will be carried over to the build. In particular, be wary of packages with compiled code, such as modules/rtm (PEcAnRTM) – the compiled .o, .so, .mod, etc. files from compilation of such packages will carry over into the build, which can cause conflicts if the package was also built locally.

The docker-compose.override.yml is useful for some other local modifications. For instance, the following adds a custom ED2 “develop” model container.

services:
  # ...
  ed2devel:
    image: pecan/model-ed2-develop:latest
    build:
      context: ../ED2  # Or wherever ED2 source code is found
    networks:
      - pecan
    depends_on:
      - rabbitmq
    volumes:
      - pecan:/data
    restart: unless-stopped

Similarly, this snippet modifies the pecan network to use a custom IP subnet mask. This is required on the PNNL cluster because its servers’ IP addresses often clash with Docker’s default IP mask.

networks:
  pecan:
    ipam:
      config:
        - subnet: 10.17.1.0/24