Skip to main content
Version: 0.6.0

Containerized Execution

The C, Python, and TypeScript target support the generation of a Dockerfile to conveniently run the generated code in a container. To enable this feature, include the docker property in your target specification, as follows:

target C { docker: true }

The generated Docker file is simply called Dockerfile and will be put in the src-gen directory. You will need to install Docker in order to use this.

Unfederated Execution​

Suppose your LF source file is src/Foo.lf. When you run lfc or use the IDE to generate code, a file called Dockerfile and a file called docker-compose.yml will appear in the src_gen/Foo directory, see Structure of an LF project for more info.

Using docker compose up​

After running lfc, change to the directory that contains the generated sources. Then, use docker compose up --build to automatically build the Docker image and run the container. Once the container finishes execution, use docker compose down in the same directory where docker-compose.yml is located to remove the container.

Using docker build and docker run​

You can also build the image and run the container in separate steps. Again, make sure that you have changed directory to the location of the Dockerfile. Then issue the command:

   docker build -t foo .

This will create a Docker image with tag foo. The tag is required to be all lower-case letters. By convention, we advise using the LF source file name, converted to lower case.

You can then use this tag to run the image in a container:

   docker run -t --rm foo

The -t option creates a pseudo terminal, which is necessary for you to see any output produced by your program to stdout. If your program also reads from stdin, then you will need to give the -i option as well, or combine the two as it.

The --rm option is important. This removes the container upon completion of the run. If you omit this option, the container will continue to exist even after your program has terminated. You can alternatively remove the container after the run using docker rm.

If you wish for your program to run in the background, give a -d option as well (for "detached"). In this case, you will not see any output from your run.

The above run command can include any supported command-line arguments to the LF program. For example, to specify a logical timeout, you can do this:

    docker run -t --rm foo --timeout 20 sec

Federated Execution​

Suppose your LF source file is src/Fed.lf. When you run lfc or use the IDE to generate code, a file called Dockerfile is created for each federate alongside its sources. Just like in the unfederated case, a single docker-compose.yml will appear in the src_gen/Fed directory, see Structure of an LF project for more info.

Using docker compose up​

Change directory to where the docker-compose.yml is located, and simply run:

    docker compose up --build 

This will build images for all the federates (and the RTI, which is pulled from Docker Hub), and run them on jointly on a shared network.

Using docker build and docker run​

You can also build the images and run the containers in separate steps, for each container individually.

To build a particular federate that we assume is called foo, change directory to src-gen/Fed/federate__foo (there should be a Dockerfile in this directory). Then issue the command:

   docker build -t foo .

Assuming there is one more federate in the program that is called bar, change directory to src-gen/Fed/federate__bar and run:

   docker build -t bar .

To pull the RTI from DockerHub, run this command:

   docker pull lflang/rti:rti

Now, create a named network on which to run your federation. For example, to create a network named lf, do this:

    docker network create lf

You can then launch the RTI on this network (do this in a separate terminal):

     docker run -t --rm --name rti --network lf lflang/rti:rti -n 2 -i 1234

Here, the -n 2 indicates that the total number of federates is two and -i 1234 assigns an identifier for the federation.

The federates foo and bar, the images of which have already been built, can be started using the following commands:

docker run -t --rm --network lf foo
docker run -t --rm --network lf bar

Configuration Options​

You can further customize the generated Docker file through the docker target property. Instead of just enabling Docker support using true, specify configuration options in a dictionary.

Select a Base Image​

You can specify a base image (FROM) as follows:

target C { docker: {FROM: "alpine:latest"} }

This specifies that the base image is the latest version of alpine, a very small Linux. In fact, alpine:latest is the default value for this option, so you only need to specify this option if you need something other than alpine:latest.

Select an RTI​

By default, an image is pulled from DockerHub that contains the RTI. An alternative image can be specified using the rti-image entry as follows:

target C { docker: {rti-image: "lflang/rti:latest"} }
note

The value of the FROM and rti-image entry should follow Docker's <user-name>/<image-name>:<tag-name> naming convention for image names. Docker will resolve the name and pull the image from your local registry, or, if it cannot be found, from DockerHub.