Creating a container backend Contribution

A backend contribution can be used to provide services to frontend contributions or run any kind of background tasks. One example could be a Gripper Open button in the frontend UI that needs to send the Open command to a Gripper device controlled by a backend contribution.

Docker containers are used for backend contributions and must be defined in the ‘manifest.yaml’ file, see How to configure, package and install a contribution.

The content of container contributions can be written using any programming language that supports creating rest end points or any other service accessible using ports and a protocols, like ROS2 nodes and more.

Creating a Dockerfile containing a REST end point

In this example we will show how to make a docker container contribution. This will include a dockerfile, a simple rest end point written in python, and some other necessary details to create a fully functioning web server in a docker container contribution.

A dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Usually an image is build upon another image. In our example we extend an image containing python on an Alpine Linux contribution.

Example of a dockerfile used in the simple-docker sample:

FROM python:3.8-alpine

# Install Flask
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application into the image
COPY src/simple-rest-api.py .

# Tell Flask where to load the application from
ENV FLASK_APP simple-rest-api.py

# Expose Flask's default port
EXPOSE 5000

# Run the REST service
ENTRYPOINT ["flask", "run", "--host", "0.0.0.0", "--port", "5000"]

As seen in the dockerfile, pip is used to install the packages needed by our simple-rest-api.py flask application. Port 5000 is exposed and used by Flask, so the same port needs to be defined in the manifest.yaml file together with the http protocol used. The Dockerfile needs to have an entrypoint that starts the flask web server when the container is running. The manifest.yaml also defines the mount point /mount for data-storage. Files saved will be stored in this mount.

Communicating with a docker container contribution

A Web frontend contribution can communicate with a container contribution using the URCapX ID (vendorID + urcapID), container-id and ingress configuration defined in the URCaps manifest.yaml.

The actual URL to use from the frontend is defined as: <vendorID>/<urcapID>/<container-id>/<ingress-id>.

The simple-docker URCapX manifest.yaml defines the following parameters

...
  vendorID: universal-robots
  urcapID: simple-docker
...
artifacts:
  containers:
    - id: simple-docker-backend
      image: simple-docker-backend
      ingress:
        - id: rest-api
          containerPort: 5000
          protocol: http
          proxyUrl: /
      ...

With this configuration a frontend contribution will be able to reach the REST API at: universal-robots/simple-docker/simple-docker-backend/rest-api

Note that the ingress configuration also defines that the REST service should communicate on port 5000 inside the container.

See How to configure, package and install a contribution for details on how to configure an URCapX and ingress in the manifest.yaml

Creating the container contribution URL using the contribution API

Common for all frontend contribution APIs (program nodes, application nodes etc.) is the API method: getContainerContributionURL. This method takes the relevant URCapX parameters and returns a valid URL for a container contribution.

  • getContainerContributionURL(vendorID, urcapID, container-name, ingress-name) - returns string

Highlighted use of the getContainerContributionURL in the simple-docker sample

simple-docker-application-node.component.ts

...
import { ApplicationPresenterAPI } from '@universal-robots/contribution-api';
import { URCAP_ID, VENDOR_ID } from '../../../generated/contribution-constants';
...
@Input() applicationAPI: ApplicationPresenterAPI;
private readonly backendProtocol = 'http:';
private backendUrl: string;

ngOnChanges(changes: SimpleChanges): void {
    ...
    if (changes?.applicationAPI?.currentValue && changes.applicationAPI.firstChange) {
        this.backendUrl = this.applicationAPI.getContainerContributionURL(VENDOR_ID, URCAP_ID, 'simple-docker-backend', 'rest-api');
    }
}
...
fetchBackendData(): void {
    this.httpClient
        .get<string>(`${this.backendProtocol}//${this.backendUrl}/`, this.httpOptions)
        .subscribe((data) => {
            // Emit value to update UI
            this.messageEmitter$.next(data);
        });
}
...

See the simple-docker sample for all the required files and see How to configure, package and install a contribution on how to package and install.

To create a new empty Docker container contribution project use the URCap Contribution Generator, see How to work with the URPlus Contribution Generator

Additional Resources

Dockerfile reference

Flask