Dockerfile and Friends or How to Run and Collaborate to ObjectScript Projects on InterSystems IRIS
Tags: programming, tutorial, git, docker
Published: 2023-08-16 16:30:55

Hi Developers!

Many of you publish your InterSystems ObjectScript libraries on [Open Exchange](https://openexchange.intersystems.com/) and Github.

But what do you do to ease the usage and collaboration to your project for developers?

In this article, I want to introduce the way how to introduce an easy way to launch and contribute to any ObjectScript project just by copying a standard set of files to your repository.

Let's go!

<!--break-->

**TLDR** - copy these files from [the repository](https://github.com/intersystems-community/objectscript-docker-template) into your repository:

[Dockerfile](https://github.com/intersystems-community/objectscript-docker-template/blob/master/Dockerfile)

[docker-compose.yml](https://github.com/intersystems-community/objectscript-docker-template/blob/master/docker-compose.yml)

[Installer.cls](https://github.com/intersystems-community/objectscript-docker-template/blob/master/Installer.cls)

[iris.script](https://github.com/intersystems-community/objectscript-docker-template/blob/master/iris.script)

[settings.json](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.vscode/settings.json "settings.json"){#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

[.dockerignore](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.dockerignore ".dockerignore"){#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}  
[.gitattributes](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.gitattributes ".gitattributes"){#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}  
[.gitignore](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.gitignore ".gitignore"){#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

And you get the standard way to launch and collaborate to your project. Below is the long article on how and why this works.

**NB:** In this article, we will consider projects which are runnable on InterSystems IRIS 2019.1 and newer.

**Choosing the launch environment for InterSystems IRIS projects**

Usually, we want a developer to try the project/library and be sure that this will be fast and safe exercise.

IMHO the ideal approach to launch anything new fast and safe is the Docker container which gives a developer a guarantee that anything he/she launches, imports, compiles and calculates is safe for the host machine and no system or code would be destroyed or spoiled. If something goes wrong you just stop and remove the container. If the application takes an enormous amount of disk space - you wipe out it with the container and your space is back. If an application spoils the database configuration - you just delete the container with spoiled configuration. Simple and safe like that.

Docker container gives you safety and standardization.

The simplest way to run vanilla InterSystems IRIS Docker container is to run an [IRIS Community Edition image](https://hub.docker.com/_/intersystems-iris-data-platform/plans/222f869e-567c-4928-b572-eb6a29706fbd?tab=instructions):

1. Install [Docker desktop](https://www.docker.com/products/docker-desktop) 

2. Run in OS terminal the following:

<pre>docker run --rm -p 52773:52773 --init --name my-iris store/intersystems/iris-community:2020.1.0.199.0</pre>

3. Then open Management portal in your host browser on:

<http://localhost:52773/csp/sys/UtilHome.csp>

4. Or open a terminal to IRIS:

    docker exec -it my-iris iris session IRIS

5. Stop IRIS container when you don't need it:

    docker stop my-iris

OK! We run IRIS in a docker container. But you want a developer to install your code into IRIS and maybe make some settings. This is what we will discuss below.

**Importing ObjectScript files**

The simplest InterSystems ObjectScript project can contain a set of ObjectScript files like classes, routines, macro, and globals. Check the article on [the naming convention](https://community.intersystems.com/post/objectscript-package-manager-naming-convention) and [proposed folder structure](https://community.intersystems.com/post/simplified-objectscript-source-folder-structure-package-manager).

The question is how to import all this code into an IRIS container?

Here is the momennt where Dockerfile helps us which we can use to take the vanilla IRIS container and import all the code from a repository to IRIS and do some settings with IRIS if we need. We need to add a Dockerfile in the repo.

Let's examine the [Dockerfile](https://github.com/intersystems-community/objectscript-docker-template/blob/master/Dockerfile) from [ObjectScript template](https://github.com/intersystems-community/objectscript-docker-template) repo:

<pre>ARG IMAGE=store/intersystems/irishealth:2019.3.0.308.0-community
ARG IMAGE=store/intersystems/iris-community:2019.3.0.309.0
ARG IMAGE=store/intersystems/iris-community:2019.4.0.379.0
ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0
FROM $IMAGE

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src
COPY iris.script /tmp/iris.script # run iris and initial 

RUN iris start IRIS \
    && iris session IRIS &amp;lt; /tmp/iris.script</pre>

 

First ARG lines set the $IMAGE variable - which we will use then in FROM. This is suitable to test/run the code in different IRIS versions switching them just by what is the last line before FROM to change the $IMAGE variable. 

Here we have: 

<pre>ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0

FROM $IMAGE</pre>

This means that we are taking IRIS 2020 Community Edition build 199.

We want to import the code from the repository - that means we need to copy the files from a repository into a docker container. The lines below help to do that:

<pre>USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src</pre>

<span style="font-family:Courier New,Courier,monospace;">USER root</span> - here we switch user to a root to create a folder and copy files in docker.

<span style="font-family:Courier New,Courier,monospace;">WORKDIR  /opt/irisapp -</span> in this line we setup the workdir in which we will copy files.

<span style="font-family:Courier New,Courier,monospace;">RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp   </span>-  here we give the rights to irisowner user and group which are run IRIS.

<span style="font-family:Courier New,Courier,monospace;">USER irisowner -</span> switching user from root to irisowner

<span style="font-family:Courier New,Courier,monospace;">COPY Installer.cls .  -</span> coping [Installer.cls](https://github.com/intersystems-community/objectscript-docker-template/blob/master/Installer.cls) to a root of workdir. Don't miss the dot, here.

<span style="font-family:Courier New,Courier,monospace;">COPY src src </span>- copy source files from [src folder in the repo](https://github.com/intersystems-community/objectscript-docker-template/tree/master/src/) to src folder in workdir in the docker.

In the next block we run the initial script, where we call installer and ObjectScript code:

<pre>COPY iris.script /tmp/iris.script # run iris and initial 
RUN iris start IRIS \
    && iris session IRIS &amp;lt; /tmp/iris.script</pre>

<span style="font-family:Courier New,Courier,monospace;">COPY iris.script /</span> - we copy iris.script into the root directory. It contains ObjectScript we want to call to setup the container.

<span style="font-family:Courier New,Courier,monospace;">RUN iris start IRIS\</span> <span> </span>- start IRIS

<span style="font-family:Courier New,Courier,monospace;">&& iris session IRIS < /tmp/iris.script - </span>start IRIS terminal and input the initial ObjectScript to it.

Fine! We have the Dockerfile, which imports files in docker. But we faced two other files: installer.cls and iris.script Let's examine it.

[**Installer.cls**](https://github.com/intersystems-community/objectscript-docker-template/blob/master/Installer.cls)

    Class App.Installer
    {
    
    XData setup
    {
    <Manifest>
      <Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>
      <Default Name="Namespace" Value="IRISAPP"/>
      <Default Name="app" Value="irisapp" />
    
      <Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">
    
        <Configuration>
          <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>
    
          <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
        </Configuration>
        <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
           
        />
      </Namespace>
    
    </Manifest>
    }
    
    ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
    {
      #; Let XGL document generate code for this method. 
      Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup")
    }
    
    }

Frankly, we do not need Installer.cls to import files. This could be done with one line. But often besides importing code we need to setup the CSP app, introduce security settings, create databases and namespaces.

In this Installer.cls we create a new database and namespace with the name IRISAPP and create the default /csp/irisapp application for this namespace.

All this we perform in <Namespace> element:

<pre>&amp;lt;Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    &amp;lt;Configuration>
      &amp;lt;Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      &amp;lt;Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    &amp;lt;/Configuration>
    &amp;lt;CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  &amp;lt;/Namespace></pre>

And we import files all the files from SourceDir with Import tag:

<pre>&amp;lt;Import File="${SourceDir}" Flags="ck" Recurse="1"/><span> </span></pre>

SourceDir here is a variable, which is set to the current directory/src folder:

<pre>&amp;lt;Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/></pre>

Installer.cls with these settings gives us confidence, that we create a clear new database IRISAPP in which we import arbitrary ObjectScript code from src folder.

[iris.script](https://github.com/intersystems-community/objectscript-docker-template/blob/master/iris.script)

Here you are welcome to provide any initial ObjectScript setup code you want to start your IRIS container.

E.g. here we load and run installer.cls and then we make UserPasswords forever just to avoid the first request to change the password cause we don't need this prompt for development.

    ; run installer to create namespace
    do $SYSTEM.OBJ.Load("/opt/irisapp/Installer.cls", "ck")
    set sc = ##class(App.Installer).setup()  zn "%SYS"
    Do ##class(Security.Users).UnExpireUserPasswords("*") ; call your initial methods here
    halt

[docker-compose.yml](https://github.com/intersystems-community/objectscript-docker-template/blob/master/docker-compose.yml)

Why do we need docker-compose.yml - couldn't we just build and run the image just with Dockerfile? Yes, we could. But docker-compose.yml simplifies the life.

Usually, docker-compose.yml is used to launch several docker images connected to one network.

docker-compose.yml could be used to also make launches of one docker image easier when we deal with a lot of parameters. You can use it to pass parameters to docker, such as ports mapping, volumes, VSCode connection parameters.

<pre>version: '3.6' 
services:
  iris:
    build: 
      context: .
      dockerfile: Dockerfile
    restart: always
    ports: 
      - 51773
      - 52773
      - 53773
    volumes:
      - ~/iris.key:/usr/irissys/mgr/iris.key
      - ./:/irisdev/app</pre>

Here we declare service iris, which uses docker file Dockerfile and which exposes the following ports of IRIS: 51773, 52773, 53773. Also this service maps two volumes: iris.key from home directory of host machine to IRIS folder where it is expected and it maps the root folder of source code to /irisdev/app folder.

Docker-compose gives us the shorter and unified command to build and run the image whatever parameters you setup in docker compose.

in any case, the command to build and launch the image is:

<pre>$ docker-compose up -d</pre>

 and to open IRIS terminal:

<pre>$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER></pre>

Also, docker-compose.yml helps to set up the connection for VSCode ObjectScript plugin.

[.vscode/settings.json](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.vscode/settings.json)

The part, which relates to ObjectScript addon connection settings is this:

<pre>{
    "objectscript.conn" :{
      "ns": "IRISAPP",
      "active": true,
      "docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }
    }     

}</pre>

Here we see the settings, which are different from default settings of VSCode ObjectScript plugin.

Here we say, that we want to connect to IRISAPP namespace (which we create with Installer.cls):

<pre>"ns": "IRISAPP",<span> </span></pre>

and there is a docker-compose setting, which tells, that in docker-compose file inside service "iris" VSCode will connect to the port, which 52773 is mapped to:

<pre>"docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }</pre>

If we check, what we have for 52773 we see that this is the mapped port is not defined for 52773:

<pre>ports: 
      - 51773
      - 52773
      - 53773</pre>

This means that a random available on a host machine port will be taken and VSCode will connect to this IRIS on docker via random port automatically.

**This is a very handy feature, cause it gives you the option to run any amount of docker images with IRIS on random ports and having VSCode connected to them automatically.**

What about other files?

We also have:

[.dockerignore](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.dockerignore)  - file which you can use to filter host machine files you don't want to be copied into docker image you build. Usually .git and .DS_Store are mandatory lines.

[.gitattributes](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.gitattributes) - attributes for git, which unify line endings for ObjectScript files in sources. This is very useful if the repo is collaborated by Windows and Mac/Ubuntu owners.

[.gitignore](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.gitignore) - files, which you don't want git to track the changes history for. Typically some hidden OS level files, like .DS_Store.

Fine!

How to make your repository docker-runnable and collaboration friendly?

 

1. Clone [this repository](https://github.com/intersystems-community/objectscript-docker-template).

2. Copy all this files:

[Dockerfile](https://github.com/intersystems-community/objectscript-docker-template/blob/master/Dockerfile)

[docker-compose.yml](https://github.com/intersystems-community/objectscript-docker-template/blob/master/docker-compose.yml)

[Installer.cls](https://github.com/intersystems-community/objectscript-docker-template/blob/master/Installer.cls)

[iris.script](https://github.com/intersystems-community/objectscript-docker-template/blob/master/iris.script)

[settings.json](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.vscode/settings.json "settings.json"){#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

[.dockerignore](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.dockerignore ".dockerignore"){#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}  
[.gitattributes](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.gitattributes ".gitattributes"){#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}  
[.gitignore](https://github.com/intersystems-community/objectscript-docker-template/blob/master/.gitignore ".gitignore"){#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

to your repository.

Change [this line in Dockerfile](https://github.com/intersystems-community/objectscript-docker-template/blob/10f4422c105d5c75111fde16a184a83f5ff86d06/Dockerfile#L15) to match the directory with ObjectScript in the repo you want to import into IRIS (or don't change if you have it in /src folder).

That's it. And everyone (and you too) will have your code imported into IRIS in a new IRISAPP namespace.

**How will people launch your project**

the algorithm to execute any ObjectScript project in IRIS would be:

1. Git clone the project locally

2. Run the project:

<pre>$ docker-compose up -d</pre>

<pre>$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>zn "IRISAPP"</pre>

**How would any the developer contribute to your project **

1. Fork the repository and git clone the forked repo locally

2. Open the folder in VSCode (they also need [Docker](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker) and [ObjectScript](https://marketplace.visualstudio.com/items?itemName=daimor.vscode-objectscript&ssr=false#review-details) extensions are installed in VSCode)

3. Right-click on docker-compose.yml->Restart - [VSCode ObjectScript](https://openexchange.intersystems.com/package/VSCode-ObjectScript) will automatically connect and be ready to edit/compile/debug

4. Commit, Push and Pull request changes to your repository

Here is the short gif on how this works:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2xkqn5pdu0enpukj3m3.gif)

That's it! Happy coding!