Background
The Canton documentation includes a Canton Getting Started example with the following topology.
Notice that the two participants and the domain are running in the same Canton instance. Also notice that the Canton App is running directly on the host computer.
While working through that example I wondered, “What would it take to setup this example within three Docker containers?” The following diagram illustrates that same example with each node (two participants and one domain) running in its own, separate Docker container:
This article shows what I did to set that up. This is strictly for experimentation on your local machine, not for production purposes.
Tool versions
When I did this, I was using:
- digitalasset/canton-open-source:2.5.0
- Daml SDK, v. 2.5.0
- Docker, v. 20.10.21
- Docker Compose, v. 2.13.0
- macOS Ventura 13.1
Launch the containers
-
Create three Canton configuration files (one for each of the Canton applications.)
-
Use
0.0.0.0
for the address binding (to play nicely with the Docker networking) -
mydomain.conf
canton { domains { mydomain { storage.type = memory public-api.port = 5018 public-api.address = 0.0.0.0 admin-api.port = 5019 admin-api.address = 0.0.0.0 } } }
-
participant1.conf
canton { participants { participant1 { storage.type = memory admin-api.port = 5012 admin-api.address = 0.0.0.0 ledger-api.port = 5011 ledger-api.address = 0.0.0.0 } } }
-
participant2.conf
canton { participants { participant2 { storage.type = memory admin-api.port = 5022 admin-api.address = 0.0.0.0 ledger-api.port = 5021 ledger-api.address = 0.0.0.0 } } }
-
-
Create two Canton bootstrap files (one for each of the Canton participants.) Each bootstrap file should do the following:
-
Connect the participants to the domain.
-
Upload the DAR (which is already included in the Canton Open Source image.)
-
Enable the parties for the participants.
-
Generate a Navigator configuration file (for our convenience when later launching Navigator.)
-
participant1.canton
nodes.local.start() participant1.domains.connect("mydomain", "http://mydomain:5018") participant1.dars.upload("dars/CantonExamples.dar") participant1.parties.enable("Alice", waitForDomain=DomainChoice.All) utils.generate_navigator_conf(participant1, Some("./host/ui-backend-participant1.conf"))
-
participant2.canton
nodes.local.start() participant2.domains.connect("mydomain", "http://mydomain:5018") participant2.dars.upload("dars/CantonExamples.dar") participant2.parties.enable("Bob", waitForDomain=DomainChoice.All) participant2.parties.enable("Bank", waitForDomain=DomainChoice.All) utils.generate_navigator_conf(participant2, Some("./host/ui-backend-participant2.conf"))
-
-
Create a docker-compose.yaml file.
-
Use the
digitalasset/canton-open-source
image. -
Make the local folder available within the container (so that we can conveniently provide configuration files to the container.)
-
Launch the Canton service in daemon mode, using the appropriate configuration and bootstrap file for each node.
-
Expose the various ports (so that the containers can communicate with each other.)
-
Publish the Admin and Ledger API ports to the host (so that we can connect with a Canton Console and Navigator.)
-
docker-compose.yaml
services: mydomain: image: digitalasset/canton-open-source:2.5.0 volumes: - ./:/canton/host/:rw entrypoint: bin/canton command: daemon --config "host/mydomain.conf" --log-profile container expose: - 5018 - 5019 ports: - 5018:5018 - 5019:5019 participant1: image: digitalasset/canton-open-source:2.5.0 volumes: - ./:/canton/host/:rw entrypoint: bin/canton command: daemon --config "host/participant1.conf" --bootstrap "host/participant1.canton" --log-profile container expose: - 5011 - 5012 ports: - 5011:5011 - 5012:5012 participant2: image: digitalasset/canton-open-source:2.5.0 volumes: - ./:/canton/host/:rw entrypoint: bin/canton command: daemon --config "host/participant2.conf" --bootstrap "host/participant2.canton" --log-profile container expose: - 5021 - 5022 ports: - 5021:5021 - 5022:5022
-
-
Ensure that your Docker engine is running (e.g, launch Docker Desktop) and then run
docker compose up --detach
. The three running containers will look something like the following within the Docker Desktop window:
Connect with Canton Console
-
Create a remote.conf file for the three nodes.
-
remote.conf
canton { remote-domains { mydomain { admin-api { address = "localhost" port = "5019" } public-api { address = "localhost" port = "5018" } } } remote-participants { participant1 { admin-api { address = "localhost" port = "5012" } ledger-api { address = "localhost" port = "5011" } } participant2 { admin-api { address = "localhost" port = "5022" } ledger-api { address = "localhost" port = "5021" } } } }
-
-
From the host machine, start a Canton Console.
daml canton-console -c remote.conf
-
In the Canton Console, test the connectivity.
participant1.health.ping(participant2)
-
In the Canton Console, confirm the parties were created.
participant1.parties.list("Bob")
Create a contract with Navigator
-
From the host machine, start an instance of Navigator for Participant 1.
daml navigator server localhost 5011 --port 4000 --config-file ui-backend-participant1.conf --feature-user-management false
-
From the host machine, start an instance of Navigator for Participant 2.
daml navigator server localhost 5021 --port 4001 --config-file ui-backend-participant2.conf --feature-user-management false
-
In the Canton Console, get the party id for Alice.
mydomain.parties.list("Alice").head.party.toProtoPrimitive
-
In Navigator, log in as the bank and issue an Iou:Iou to Alice.
-
In Navigator, log in as Alice and see the issued Iou:Iou.
Creating an Iou:Iou for Alice on participant2, port 4001
Viewing the Iou:Iou as Alice on participant1, port 4000
Here is the GitHub branch with the source code. You can use the following command to clone it.
git clone --branch docker-canton-example https://github.com/wallacekelly-da/daml-public-demos.git