Docker .NET "Names Directory" example that connects to Microsoft SQL Server, PostgreSQL, and SQLite. Automate the building of this image and run this application on any cloud using HyperForm.
PowerShell C# HTML Other
Latest commit 9fd4932 Nov 16, 2016 @hypergrid-inc committed on GitHub Update First Name and Last Name
Permalink
Failed to load latest commit information.
Controllers first commit Aug 27, 2016
DAL first commit Aug 27, 2016
Migrations first commit Aug 27, 2016
Models
Properties
Service References/Application Insights first commit Aug 27, 2016
Views Update First Name and Last Name Nov 16, 2016
wwwroot first commit Aug 27, 2016
.bowerrc
AppSettings.cs first commit Aug 27, 2016
Dockerfile first commit Aug 27, 2016
NameDirectoryService.xproj first commit Aug 27, 2016
NameDirectoryService.xproj.user first commit Aug 27, 2016
Program.cs first commit Aug 27, 2016
Project_Readme.html first commit Aug 27, 2016
README.md
Startup.cs first commit Aug 27, 2016
appsettings.json first commit Aug 27, 2016
bower.json first commit Aug 27, 2016
bundleconfig.json first commit Aug 27, 2016
dchq-docker-aspnet-example_0825.zip
project.json
project.lock.json first commit Aug 27, 2016
web.config first commit Aug 27, 2016

README.md

HyperForm - Docker .NET Example

This project includes the source code to a simple Microsoft .NET application called "Names Directory" that allows users to view, add and remove names stored on a connected database.

The purpose of this project is to provide detailed steps for running Microsoft .NET applications on Docker containers. The Dockerfile included in this project allows users to build their custom image. However, this application also enables users to leverage environment variables to specify the database connection details and to use dotnet ef database update to automatically initialize the connected database - even if it's initially empty.

To run & manage this Docker .NET "Names Directory" application on 18 different clouds and virtualization platforms (including HyperGrid, Hyper-V, vSphere, OpenStack, AWS, Rackspace, Microsoft Azure, DigitalOcean, IBM SoftLayer, etc.), make sure that you either:

Customize and Run

Customize & Run all the published Docker .NET application templates and many other templates (including multi-tier Java application stacks, Python, Ruby, PHP, Mongo Replica Set Cluster, Drupal, Wordpress, MEAN.JS, etc.)

Table of Contents

 

A Step by Step Guide for Dockerizing a .NET Application that Connects to SQLite, PostgreSQL and Microsoft SQL Server

.NET with Embedded-SQLite

Customize and Run

dotnet:
  image: dchq/dotnet-names-directory:latest
  publish_all: true
  #ports:
  #  - 5000:5000
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=sqlight
    - database_url=Filename=.\\sqlight_db.db

2-Tier .NET (Nginx-Embedded-SQLite)

Customize and Run

nginx:
  image: nginx:latest
  publish_all: true
  mem_min: 50m
  host: host1
  plugins:
    - !plugin
      id: 0H1Nk
      restart: true
      lifecycle: on_create, post_scale_out:dotnet, post_scale_in:dotnet
      arguments:
        # Use container_private_ip if you're using Docker networking
        - servers=server {{dotnet | container_private_ip}}:5000;
        # Use container_hostname if you're using Weave networking
        #- servers=server {{dotnet | container_hostname}}:5000;
dotnet:
  image: dchq/dotnet-names-directory:latest
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=sqlight
    - database_url=Filename=.\\sqlight_db.db

2-Tier .NET (Apache HTTP-Embedded-SQLite)

Customize and Run

http-lb:
  image: httpd:latest
  publish_all: true
  mem_min: 50m
  host: host1
  plugins:
    - !plugin
      id: uazUi
      restart: true
      lifecycle: on_create, post_scale_out:dotnet, post_scale_in:dotnet
      arguments:
        # Use container_private_ip if you're using Docker networking
        - BalancerMembers=BalancerMember http://{{dotnet | container_private_ip}}:5000
        # Use container_hostname if you're using Weave networking
        #- BalancerMembers=BalancerMember http://{{dotnet | container_hostname}}:5000
dotnet:
  image: dchq/dotnet-names-directory:latest
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=sqlight
    - database_url=Filename=.\\sqlight_db.db

2-Tier .NET with PostgreSQL

Customize and Run

dotnet:
  image: dchq/dotnet-names-directory:latest
  publish_all: true
  #ports:
  #  - 5000:5000
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=postgres
    - database_url=Host={{postgres | container_private_ip}};Database={{postgres | POSTGRES_DB}};Username={{postgres | POSTGRES_USER}};Password={{postgres | POSTGRES_PASSWORD}}
  plugins:
    - !plugin
      id: hHjF0
      lifecycle: post_create
      restart: false
postgres:
  image: postgres:latest
  host: host1
  mem_min: 300m
  environment:
    - POSTGRES_DB=NameDirectory
    - POSTGRES_USER=root
    - POSTGRES_PASSWORD={{alphanumeric | 8}}

3-Tier .NET (Nginx-.NET-PostgreSQL)

Customize and Run

nginx:
  image: nginx:latest
  publish_all: true
  mem_min: 50m
  host: host1
  plugins:
    - !plugin
      id: 0H1Nk
      restart: true
      lifecycle: on_create, post_scale_out:dotnet, post_scale_in:dotnet
      arguments:
        # Use container_private_ip if you're using Docker networking
        - servers=server {{dotnet | container_private_ip}}:5000;
        # Use container_hostname if you're using Weave networking
        #- servers=server {{dotnet | container_hostname}}:5000;
dotnet:
  image: dchq/dotnet-names-directory:latest
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=postgres
    - database_url=Host={{postgres | container_private_ip}};Database={{postgres | POSTGRES_DB}};Username={{postgres | POSTGRES_USER}};Password={{postgres | POSTGRES_PASSWORD}}
  plugins:
    - !plugin
      id: hHjF0
      lifecycle: post_create
      restart: false
postgres:
  image: postgres:latest
  host: host1
  mem_min: 300m
  environment:
    - POSTGRES_DB=NameDirectory
    - POSTGRES_USER=root
    - POSTGRES_PASSWORD={{alphanumeric | 8}}

3-Tier .NET (Apache HTTP-.NET-PostgreSQL)

Customize and Run

http-lb:
  image: httpd:latest
  publish_all: true
  mem_min: 50m
  host: host1
  plugins:
    - !plugin
      id: uazUi
      restart: true
      lifecycle: on_create, post_scale_out:dotnet, post_scale_in:dotnet
      arguments:
        # Use container_private_ip if you're using Docker networking
        - BalancerMembers=BalancerMember http://{{dotnet | container_private_ip}}:5000
        # Use container_hostname if you're using Weave networking
        #- BalancerMembers=BalancerMember http://{{dotnet | container_hostname}}:5000
dotnet:
  image: dchq/dotnet-names-directory:latest
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=postgres
    - database_url=Host={{postgres | container_private_ip}};Database={{postgres | POSTGRES_DB}};Username={{postgres | POSTGRES_USER}};Password={{postgres | POSTGRES_PASSWORD}}
  plugins:
    - !plugin
      id: hHjF0
      lifecycle: post_create
      restart: false
postgres:
  image: postgres:latest
  host: host1
  mem_min: 300m
  environment:
    - POSTGRES_DB=NameDirectory
    - POSTGRES_USER=root
    - POSTGRES_PASSWORD={{alphanumeric | 8}}

.NET Connecting to MS SQL Server

Customize and Run

dotnet:
  image: dchq/dotnet-names-directory:latest
  publish_all: true
  #ports:
  #  - 5000:5000
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=mssql
    - database_url=Server=tcp:dchq-sql-server.database.windows.net,1433;Initial Catalog=NameDirectory;Persist Security Info=False;User ID={your_username};Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
  plugins:
    - !plugin
      id: hHjF0
      lifecycle: post_create
      restart: false

2-Tier .NET (Nginx Connecting to MS SQL Server)

Customize and Run

nginx:
  image: nginx:latest
  publish_all: true
  mem_min: 50m
  host: host1
  plugins:
    - !plugin
      id: 0H1Nk
      restart: true
      lifecycle: on_create, post_scale_out:dotnet, post_scale_in:dotnet
      arguments:
        # Use container_private_ip if you're using Docker networking
        - servers=server {{dotnet | container_private_ip}}:5000;
        # Use container_hostname if you're using Weave networking
        #- servers=server {{dotnet | container_hostname}}:5000;
dotnet:
  image: dchq/dotnet-names-directory:latest
  publish_all: false
  #ports:
  #  - 5000:5000
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=mssql
    - database_url=Server=tcp:dchq-sql-server.database.windows.net,1433;Initial Catalog=NameDirectory;Persist Security Info=False;User ID={your_username};Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
  plugins:
    - !plugin
      id: hHjF0
      lifecycle: post_create
      restart: false

2-Tier .NET (Apache HTTP Connecting to MS SQL Server)

Customize and Run

http-lb:
  image: httpd:latest
  publish_all: true
  mem_min: 50m
  host: host1
  plugins:
    - !plugin
      id: uazUi
      restart: true
      lifecycle: on_create, post_scale_out:dotnet, post_scale_in:dotnet
      arguments:
        # Use container_private_ip if you're using Docker networking
        - BalancerMembers=BalancerMember http://{{dotnet | container_private_ip}}:5000
        # Use container_hostname if you're using Weave networking
        #- BalancerMembers=BalancerMember http://{{dotnet | container_hostname}}:5000
dotnet:
  image: dchq/dotnet-names-directory:latest
  publish_all: false
  #ports:
  #  - 5000:5000
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=mssql
    - database_url=Server=tcp:dchq-sql-server.database.windows.net,1433;Initial Catalog=NameDirectory;Persist Security Info=False;User ID={your_username};Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
  plugins:
    - !plugin
      id: hHjF0
      lifecycle: post_create
      restart: false

Environment Variable Bindings Across Images

A user can create cross-image environment variable bindings by making a reference to another image’s environment variable or container values. For example, the database_url environment variable in the .NET container is referencing the container private IP, database name, database username, and database password of the PostgreSQL container in order allow .NET to establish a connection with the database -- database_url=Host={{postgres | container_private_ip}};Database={{postgres | POSTGRES_DB}};Username={{postgres | POSTGRES_USER}};Password={{postgres | POSTGRES_PASSWORD}}

The main advantage here is that users do not have to hard-code these values and thus preventing container name or port conflicts. Moreover, users can randomize sensitive information (like passwords) using {{alphanumeric | 8}} closures and pass this information to other containers at run-time without worrying about visibility to passwords.

Here is a list of supported environment variable values:

  • {{alphanumeric | 8}} – creates a random 8-character alphanumeric string. This is most useful for creating random passwords.
  • {{Image Name | ip}} – allows you to enter the host IP address of a container as a value for an environment variable. This is most useful for allowing the middleware tier to establish a connection with the database.
  • {{Image Name | container_hostname}} or {{Image Name | container_ip}} – allows you to enter the name of a container as a value for an environment variable. This is most useful for allowing the middleware tier to establish a secure connection with the database (without exposing the database port).
  • {{Image Name | container_private_ip}} – allows you to enter the internal IP of a container as a value for an environment variable. This is most useful for allowing the middleware tier to establish a secure connection with the database (without exposing the database port).
  • {{Image Name | port_Port Number}} – allows you to enter the Port number of a container as a value for an environment variable. This is most useful for allowing the middleware tier to establish a connection with the database. In this case, the port number specified needs to be the internal port number – i.e. not the external port that is allocated to the container. For example, {{PostgreSQL | port_5432}} will be translated to the actual external port that will allow the middleware tier to establish a connection with the database.
  • {{Image Name | Environment Variable Name}} – allows you to enter the value an image’s environment variable into another image’s environment variable. The use cases here are endless – as most multi-tier applications will have cross-image dependencies.

Plug-ins to Configure Web Servers and .NET Servers at Request Time & Post-Provision

HyperForm simplifies the containerization of enterprise applications through an advanced application composition framework that extends Docker Compose supporting

  • advanced plug-ins that can be invoked at more than 20 different lifecycle stages to enable service discovery, on-the-fly containerization and application storage automation, and
  • data injection to support complex application dependencies.

Across all these application templates, you will notice that some of the containers are invoking plug-ins at different life-cycle stages in order to configure the container.

The Plug-ins framework, which relies on custom scripts that can be written in BASH, PowerShell, Perl, Ruby or Python, enables advanced application deployment and in this case, facilitates both service discovery for the web servers (Nginx and Apache HTTP) and automatic database initialization for the .NET application.

These plug-ins can be created by navigating to Blueprints > Plug-ins. Once the BASH script is provided, the HyperForm agent will execute this script inside the container. A user can specify arguments that can be overridden at request time and post-provision. Anything preceded by the $ sign is considered an argument -- for example, $zip_url can be an argument that allows developers to specify the download URL for a ZIP file. This can be overridden at request time and post-provision.

The plug-in ID needs to be provided when defining the YAML-based application template. For example, to invoke a BASH script plug-in for Nginx, we would reference the plug-in ID as follows:

nginx:
  image: nginx:latest
  publish_all: true
  mem_min: 50m
  host: host1
  plugins:
    - !plugin
      id: 0H1Nk
      restart: true
      lifecycle: on_create, post_scale_out:dotnet, post_scale_in:dotnet
      arguments:
        # Use container_private_ip if you're using Docker networking
        - servers=server {{dotnet | container_private_ip}}:5000;

In the example templates, we are invoking 3 BASH script plug-ins.

  • Nginx plug-in ID is 0H1Nk.
  • Apache HTTP Server (httpd) plug-in ID is uazUi.
  • .NET Database Initialization plug-in ID is hHjF0

The service discovery framework in HyperForm provides event-driven life-cycle stages that executes custom scripts to re-configure application components. This is critical when scaling out clusters for which a load balancer may need to be re-configured or a replica set may need to be re-balanced.

You will notice that the Nginx and Apache HTTP plug-ins are getting executed during these different stages or events:

  • When the Nginx or the Apache HTTP container is created -- in this case, the container IP's of the application servers are injected into the default configuration file to facilitate the load balancing to the right services

  • When the ASP.NET application server cluster is scaled in or scale out -- in this case, the updated container IP's of the application servers are injected into the default configuration file to facilitate the load balancing to the right services

  • When the ASP.NET application servers are stopped or started -- in this case, the updated container IP's of the application servers are injected into the default configuration file to facilitate the load balancing to the right services

So the service discovery framework here is doing both service registration (by keeping track of the container IP's and environment variable values) and service discovery (by executing the right scripts during certain events or stages).

The lifecycle parameter in plug-ins allows you to specify the exact stage or event to execute the plug-in. If no lifecycle is specified, then by default, the plug-in will be execute on_create. Here are the supported lifecycle stages:

  • on_create -- executes the plug-in when creating the container
  • on_start -- executes the plug-in after a container starts
  • on_stop -- executes the plug-in before a container stops
  • on_destroy -- executes the plug-in before destroying a container
  • pre_create – executes the plug-in before creating the container
  • post_create -- executes the plug-in after the container is created and running
  • post_start[:Node] -- executes the plug-in after another container starts
  • post_stop[:Node] -- executes the plug-in after another container stops
  • post_destroy[:Node] -- executes the plug-in after another container is destroyed
  • post_scale_out[:Node] -- executes the plug-in after another cluster of containers is scaled out
  • post_scale_in[:Node] -- executes the plug-in after another cluster of containers is scaled in
  • cron(0 1 1 * * ?) – schedules the plug-in based on a specified cron expression. Here are some examples for cron expressions.
  • exec_on_machine – executes the plug-in on the underlying machine. This lifecycle can be used with other container life cycles. For example, exec_on_machine pre_create will execute the plug-in on the machine before creating the container.

In order to automatically initialize a connected database, the .NET container needs to run dotnet ef database update post-provision -- as only then is the .NET container aware of the database connection details that are passed using environment variables. Of course this plug-in will not be needed if the database connection details are hard-coded in the appsettings.json file.

https://github.com/dchqinc/docker-dotnet/blob/master/appsettings.json

With the exception of the embedded SQLite application example, all other application templates that are connected to either PostgreSQL or Microsoft SQL Server are invoking the exact same BASH script plug-in (plug-in ID: hHjF0).

Here's an example of how this plug-in is being invoked:

dotnet:
  image: dchq/dotnet-names-directory:latest
  publish_all: true
  #ports:
  #  - 5000:5000
  host: host1
  mem_min: 200m
  environment:
    - database_driverClassName=mssql
    - database_url=Server=tcp:dchq-sql-server.database.windows.net,1433;Initial Catalog=NameDirectory;Persist Security Info=False;User ID={your_username};Password={your_password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
  plugins:
    - !plugin
      id: hHjF0
      lifecycle: post_create
      restart: false

The BASH script plug-in was created by navigating to Blueprints > Plug-ins and looks something like this:

#!/bin/bash

dotnet restore
dotnet ef database update
dotnet run