Module 4: Multi containers environment

Contact service

Contact service is writtern by .Net Core 3.0 preview and use Postgres to store data. Source code is ready at https://github.com/tungphuong/crm.git.

To dockerize this service, we need 3 containers:

  • Postgres
  • Contact service
  • Database migration

Let's pull the image

$ sudo docker pull postgres:11-alpine

And run it

$ sudo docker run --rm -p 5432:5432 -d -e POSTGRES_USER=lab -e POSTGRES_PASSWORD=P@ssw0rd --name postgres.data postgres:11-alpine

Run "Contact" service

$ sudo docker run --rm -p 5000:80 -e ConnectionStrings__default="Server=postgres.data;Port=5432;Database=crm-contact;User Id=lab;Password=P@ssw0rd;" crmnow/contact-api:latest

System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (00000005, 0xFFFDFFFF): Name or service not known
   at System.Net.Dns.InternalGetHostByName(String hostName)
   at System.Net.Dns.ResolveCallback(Object context)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
   at System.Net.Dns.HostResolutionEndHelper(IAsyncResult asyncResult)
   at System.Net.Dns.EndGetHostAddresses(IAsyncResult asyncResult)
   at System.Net.Dns.<>c.<GetHostAddressesAsync>b__25_1(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---

The service cannot connect to database. So, how to connect them together. The answer relates to "Docker Network".

Docker network

Create our own netwrok

$ sudo docker network create workshop-network
1de671cb3e7b3c05679c4f4a59bb75c5856f491008d44f24b57638523c40f9f6

Next, we will run Postgres in the new network

$ sudo docker run --rm -p 5432:5432 -d -e POSTGRES_USER=lab -e POSTGRES_PASSWORD=P@ssw0rd --name postgres.data --net workshop-network  postgres:11-alpine

And try to connect our service to database server in the workshop-network network

$ sudo docker run --rm -p 5000:80 --net workshop-network -e ConnectionStrings__default="Server=postgres.data;Port=5432;Database=crm-contact;User Id=lab;Password=P@ssw0rd;" crmnow/contact-api:latest

Npgsql.PostgresException (0x80004005): 3D000: database "crm-contact" does not exist
   at Npgsql.NpgsqlConnector.<>c__DisplayClass161_0.<<ReadMessage>g__ReadMessageLong|0>d.MoveNext()

Greate, connected successfully but we still need to setup database via migration.

Docker compose

Docker compose is a tool for defining and running multi-container Docker application

Install compose-compose CLI

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

$ sudo chmod +x /usr/local/bin/docker-compose

Test the installation

$ docker-compose --version
$ docker-compose version 1.24.1, build 4667896b

Next steps, expoler one simple Docker compose file with YAML format.

version: '3.7'

services:
  postgres.data:
    image: postgres:11-alpine
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - crm

  contact-api:
    image: ${REGISTRY:-crmnow}/contact-api:${TAG:-latest}
    build:
      context: .
      dockerfile: src/Contact/CRM.Contact.Api/Dockerfile
      args:
        - feed=${NUGET_FEED:- --source "https://api.nuget.org/v3/index.json"}
    networks:
      - crm
    depends_on:
      - postgres.data

  contact-api-migration:
    image: ${REGISTRY:-crmnow}/contact-api:${TAG:-latest}
    build:
      context: .
      dockerfile: src/Contact/CRM.Contact.Api/Dockerfile
      target: migration
      args:
        - feed=${NUGET_FEED:- --source "https://api.nuget.org/v3/index.json"}
    environment:
      - ConnectionStrings__contact=Server=postgres.data;Port=5432;Database=crm-contact;User Id=lab;Password=P@ssw0rd;
    entrypoint:
      - dotnet
      - run
      - contact
    networks:
      - crm
    depends_on:
      - postgres.data

volumes:
  postgres-data:
  nexus-data:
  seq-data:

networks:
  crm:
    name: crm-network

Now the file is ready, let's see docker-compose in action Firstly, clean up environment

$ sudo docker rm -f postgres.data contact-api

$ sudo docker network rm workshop-network

Secondly, run database migration

$ cd crm

$ docker-compose -f docker-compose.yml -f docker-compose.override.yml up postgres.data contact-api-migration

contact-api-migration_1  | [10:19:09 INF] Run migration - Contact Db
contact-api-migration_1  | Master ConnectionString => Host=postgres.data;Port=5432;Database=postgres;Username=lab;Password=********
contact-api-migration_1  | Created database crm-contact
contact-api-migration_1  | [10:19:11 INF] Beginning database upgrade
contact-api-migration_1  | [10:19:11 INF] Checking whether journal table exists..
contact-api-migration_1  | [10:19:11 INF] Journal table does not exist
contact-api-migration_1  | [10:19:11 INF] Executing Database Server script 'script0001.sql'
contact-api-migration_1  | [10:19:11 INF] Checking whether journal table exists..
contact-api-migration_1  | [10:19:11 INF] Creating the "schemaversions" table
contact-api-migration_1  | [10:19:11 INF] The "schemaversions" table has been created
contact-api-migration_1  | [10:19:11 INF] Upgrade successful
crm_contact-api-migration_1 exited with code 0

It is the time to up everything.

$ cd crm

$ docker-compose -f docker-compose.yml -f docker-compose.override.yml up postgres.data contact-api

Creating network "crm-network" with the default driver
Creating volume "crm_postgres-data" with default driver
Creating volume "crm_nexus-data" with default driver
Creating volume "crm_seq-data" with default driver
Creating crm_postgres.data_1 ... done
Creating crm_contact-api-migration_1 ... done
Creating crm_contact-api_1           ...
...

[
  {
    "contactId": "6c7b66fb-d8cb-493b-a2fe-96963f81e43f",
    "firstName": "Ut enim ad minim veniam, quis nos",
    "lastName": "Duis aute irure dolor in reprehenderit in vo",
    "middleName": "ipsum dolor sit amet, c",
    "description": "m"
  },
  {
    "contactId": "78d616a1-9e0c-4c07-a089-9d64ea551dfb",
    "firstName": "dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ul",
    "lastName": "laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incid",
    "middleName": "consequat. Duis aute irure dolor in reprehender",
    "description": "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magn"
  }
]

Woohoo, everything is up now! Try playing around.