{"id":18946,"date":"2017-10-13T12:15:10","date_gmt":"2017-10-13T09:15:10","guid":{"rendered":"https:\/\/www.webcodegeeks.com\/?p=18946"},"modified":"2017-10-13T10:57:42","modified_gmt":"2017-10-13T07:57:42","slug":"service-resources-terraformed-docker-environment","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/","title":{"rendered":"Service Resources for Your Terraformed Docker Environment"},"content":{"rendered":"<p>In <a href=\"https:\/\/www.webcodegeeks.com\/devops\/shared-resources-terraformed-docker-environment-aws\/\">Part II of this series<\/a>, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to run on that infrastructure.<\/p>\n<h2>Service Resources Declaration<\/h2>\n<p>To keep this tutorial as simple as possible, for my service I am just going to run a web interface for the RDS database instance. This will demonstrate the use of a load balancer, Docker service, and database all while automatically configuring the connections between them.<\/p>\n<p>In my example, I use <a href=\"https:\/\/www.cloudflare.com\/\">Cloudflare<\/a> for DNS and CDN services to demonstrate the multi-provider capability of Terraform. However, if you don\u2019t use Cloudflare, you can leave those pieces out. I have an example of using Route53 for the DNS portion.<\/p>\n<p>At the same level as the <code>shared<\/code> folder, create a folder named <code>service<\/code>.<\/p>\n<p>In the <code>service<\/code> folder, we need to create many of the same files as we did for <code>shared<\/code> earlier:<\/p>\n<ul>\n<li>backend.tf<\/li>\n<li>main.tf<\/li>\n<li>outputs.tf<\/li>\n<li>providers.tf<\/li>\n<li>vars.tf<\/li>\n<\/ul>\n<p><code>backend.tf<\/code> should contain:<\/p>\n<pre class=\"brush:php\">\/\/ service\/backend.tf\r\nterraform {\r\n backend \"s3\" {\r\n   bucket         = \"myorg-terraform-environmentname\"\r\n   key            = \"service\/terraform.tfstate\"\r\n   region         = \"us-east-1\"\r\n   encrypt        = true\r\n   dynamodb_table = \"terraform-lock\"\r\n }\r\n}<\/pre>\n<p><code>providers.tf<\/code> should contain:<\/p>\n<pre class=\"brush:php\">\/\/ service\/providers.tf\r\nprovider \"aws\" {\r\n  region     = \"${var.aws_region}\"\r\n  access_key = \"${var.aws_access_key_id}\"\r\n  secret_key = \"${var.aws_secret_access_key}\"\r\n}\r\nprovider \"cloudflare\" {\r\n email = \"${var.cloudflare_email}\"\r\n token = \"${var.cloudflare_token}\"\r\n}<\/pre>\n<p><code>vars.tf<\/code> should contain:<\/p>\n<pre class=\"brush:php\">\/\/ service\/vars.tf\r\nvariable \"aws_region\" {}\r\nvariable \"aws_access_key_id\" {}\r\nvariable \"aws_secret_access_key\" {}\r\nvariable \"cloudflare_email\" {}\r\nvariable \"cloudflare_token\" {}\r\nvariable \"cloudflare_domain\" {}<\/pre>\n<p>With the addition of Cloudflare to this environment, you\u2019ll also need to export your Cloudflare credentials as environment variables for Terraform to pick up on:<\/p>\n<pre class=\"brush:js\">$ export TF_VAR_cloudflare_email=youremail@domain.com\r\n$ export TF_VAR_cloudflare_token=asdfadsfadsfasdfasdfsafd\r\n$ export TF_VAR_cloudflare_domain=domain.com<\/pre>\n<p>Now we need to initialize and plan for this environment to make sure everything is configured correctly.<\/p>\n<pre class=\"brush:js\">$ terraform init\r\nInitializing the backend...\r\n\r\nSuccessfully configured the backend \"s3\"! Terraform will automatically\r\nuse this backend unless the backend configuration changes.\r\n\r\nInitializing provider plugins...\r\n- Checking for available provider plugins on https:\/\/releases.hashicorp.com...\r\n- Downloading plugin for provider \"template\" (0.1.1)...\r\n- Downloading plugin for provider \"terraform\" (1.0.0)...\r\n- Downloading plugin for provider \"aws\" (0.1.4)...\r\n\r\nThe following providers do not have any version constraints in configuration,\r\nso the latest version was installed.\r\n\r\nTo prevent automatic upgrades to new major versions that may contain breaking\r\nchanges, it is recommended to add version = \"...\" constraints to the\r\ncorresponding provider blocks in configuration, with the constraint strings\r\nsuggested below.\r\n\r\n* provider.aws: version = \"~&gt; 0.1\"\r\n* provider.template: version = \"~&gt; 0.1\"\r\n* provider.terraform: version = \"~&gt; 1.0\"\r\n\r\nTerraform has been successfully initialized!\r\n\r\nYou may now begin working with Terraform. Try running \"terraform plan\" to see\r\nany changes that are required for your infrastructure. All Terraform commands\r\nshould now work.\r\n\r\nIf you ever set or change modules or backend configuration for Terraform,\r\nrerun this command to reinitialize your working directory. If you forget, other\r\ncommands will detect it and remind you to do so if necessary.\r\n\r\n$ terraform plan\r\nRefreshing Terraform state in-memory prior to plan...\r\nThe refreshed state will be used to calculate this plan, but will not be\r\npersisted to local or remote state storage.\r\n\r\ndata.terraform_remote_state.shared: Refreshing state...\r\n\r\n------------------------------------------------------------------------\r\n\r\nNo changes. Infrastructure is up-to-date.\r\n\r\nThis means that Terraform did not detect any differences between your\r\nconfiguration and real physical resources that exist. As a result, no\r\nactions need to be performed.\r\nReleasing state lock. This may take a few moments...<\/pre>\n<p>Now update <code>outputs.tf<\/code> to contain the following so we can display some information after we run <code>terraform apply<\/code> in a bit:<\/p>\n<pre class=\"brush:js\">output \"url\" {\r\n value = \"https:\/\/pma.${var.cloudflare_domain}\"\r\n}\r\noutput \"dbusername\" {\r\n value = \"${aws_db_instance.db_instance.username}\"\r\n}\r\noutput \"dbpassword\" {\r\n value = \"${aws_db_instance.db_instance.password}\"\r\n}<\/pre>\n<p>Our <code>service<\/code> environment depends on our <code>shared<\/code> environment, so we need to tell the <code>service<\/code> environment how to get access to the remote state for <code>shared<\/code>.<\/p>\n<p>Create a new file named <code>remote-shared.tf<\/code> with the following contents:<\/p>\n<pre class=\"brush:js\">\/\/ service\/remote-shared.tf\r\ndata \"terraform_remote_state\" \"shared\" {\r\n backend = \"s3\"\r\n\r\n config {\r\n   bucket = \"myorg-terraform-environmentname\"\r\n   key    = \"shared\/terraform.tfstate\"\r\n   region = \"us-east-1\"\r\n }\r\n}<\/pre>\n<p>This will create a <code>data<\/code> resource of type <code>terraform_remote_state<\/code> named <code>shared<\/code>. This provides read-only access to any <code>outputs<\/code> from the <code>shared<\/code> Terraform environment by using variable interpolation with the format: <code>\u201c${data.terraform_remote_state.shared.output_name}\u201d<\/code>.<\/p>\n<p>Let\u2019s start building out the resources needed for our service in the <code>main.tf<\/code> file. First off, we\u2019ll create the database:<\/p>\n<pre class=\"brush:js\">\/\/ service\/main.tf\r\nresource \"aws_db_instance\" \"db_instance\" {\r\n engine                  = \"mariadb\"\r\n allocated_storage       = \"8\"\r\n instance_class          = \"db.t2.micro\"\r\n name                    = \"mydatabase\"\r\n identifier              = \"mydatabase\"\r\n username                = \"dbuser\"\r\n password                = \"dbpass1234\"\r\n db_subnet_group_name    = \"${data.terraform_remote_state.shared.db_subnet_group_name}\"\r\n vpc_security_group_ids  = [\"${data.terraform_remote_state.shared.vpc_default_sg_id}\"]\r\n skip_final_snapshot     = true \/\/ &lt;- not recommended for production\r\n}<\/pre>\n<p>You\u2019ll see in our database resource we reference outputs from the shared environment to get access to the <code>db_subnet_group_name<\/code> and <code>vpc_security_group_id<\/code>.<\/p>\n<p>Next up, we\u2019ll create the Application Load Balancer. For this step, we need an SSL certificate. I recommend using Amazon\u2019s Certificate Manager (ACM) service for free and easy certs.<\/p>\n<p>You cannot use Terraform to create ACM certs because there is a separate approval process, but once one is issued, you can reference it in Terraform. If you don\u2019t already have an ACM certificate, it is easy to get one. Just go to the Certificate Manager service in the AWS console, request a new certificate for the hostname you want, and AWS will send emails to contacts from a WHOIS report for approval. Open the email and click the approve link, and it\u2019ll be immediately available for use.<\/p>\n<p>To look up the certificate, we\u2019ll use a data resource that we can later reference in the ALB listener configuration.<\/p>\n<pre class=\"brush:js\">\/\/ service\/main.tf\r\ndata \"aws_acm_certificate\" \"sslcert\" {\r\n domain = \"*.mydomain.com\"\r\n}<\/pre>\n<p>We need to create a new security group to allow public access to port 443 of the load balancer.<\/p>\n<pre class=\"brush:js\">\/\/ service\/main.tf\r\nresource \"aws_security_group\" \"public_https\" {\r\n name        = \"public-https\"\r\n description = \"Allow HTTPS traffic from public\"\r\n vpc_id      = \"${data.terraform_remote_state.shared.vpc_id}\"\r\n}\r\n\r\nresource \"aws_security_group_rule\" \"public_https\" {\r\n type              = \"ingress\"\r\n from_port         = 443\r\n to_port           = 443\r\n protocol          = \"tcp\"\r\n security_group_id = \"${aws_security_group.public_https.id}\"\r\n cidr_blocks       = [\"0.0.0.0\/0\"]\r\n}<\/pre>\n<p>Now we can create the ALB, a target group, and a listener rule for HTTPS to route to the default target group.<\/p>\n<pre class=\"brush:js\">\/\/ service\/main.tf\r\n\/*\r\n * Create application load balancer\r\n *\/\r\nresource \"aws_alb\" \"alb\" {\r\n name            = \"alb-myapp\"\r\n internal        = false\r\n security_groups = [\"${data.terraform_remote_state.shared.vpc_default_sg_id}\", \"${aws_security_group.public_https.id}\"]\r\n subnets         = [\"${data.terraform_remote_state.shared.public_subnet_ids}\"]\r\n}\r\n\r\n\/*\r\n * Create target group for ALB\r\n *\/\r\nresource \"aws_alb_target_group\" \"default\" {\r\n name     = \"tg-myapp\"\r\n port     = \"80\"\r\n protocol = \"HTTP\"\r\n vpc_id   = \"${data.terraform_remote_state.shared.vpc_id}\"\r\n\r\n stickiness {\r\n   type = \"lb_cookie\"\r\n }\r\n}\r\n\r\n\/*\r\n * Create listeners to connect ALB to target group\r\n *\/\r\nresource \"aws_alb_listener\" \"https\" {\r\n load_balancer_arn = \"${aws_alb.alb.arn}\"\r\n port              = \"443\"\r\n protocol          = \"HTTPS\"\r\n ssl_policy        = \"ELBSecurityPolicy-2016-08\"\r\n certificate_arn   = \"${data.aws_acm_certificate.sslcert.arn}\"\r\n\r\n default_action {\r\n   target_group_arn = \"${aws_alb_target_group.default.arn}\"\r\n   type             = \"forward\"\r\n }\r\n}<\/pre>\n<p>With a load balancer in place, we can create an ECS task definition and service definition to run the web interface to the database. For the task definition, we need to use a template file to dynamically provide values and render the JSON needed. So create a new file named <code>service\/task-definition.json<\/code> with the following contents. If you\u2019re familiar with ECS already, you\u2019ll notice the JSON below is not a full task definition. Instead, it is only the <code>containerDefinitions<\/code> portion of the overall definition.<\/p>\n<pre class=\"brush:js\">[\r\n {\r\n   \"volumesFrom\": [],\r\n   \"memory\": 128,\r\n   \"extraHosts\": null,\r\n   \"dnsServers\": null,\r\n   \"disableNetworking\": null,\r\n   \"dnsSearchDomains\": null,\r\n   \"portMappings\": [\r\n     {\r\n       \"hostPort\": 0,\r\n       \"containerPort\": 80,\r\n       \"protocol\": \"tcp\"\r\n     }\r\n   ],\r\n   \"hostname\": null,\r\n   \"essential\": true,\r\n   \"entryPoint\": [],\r\n   \"mountPoints\": [],\r\n   \"name\": \"web\",\r\n   \"ulimits\": null,\r\n   \"dockerSecurityOptions\": null,\r\n   \"environment\": [\r\n     {\r\n       \"name\": \"PMA_HOST\",\r\n       \"value\": \"${mysql_host}\"\r\n     },\r\n     {\r\n       \"name\": \"PMA_ABSOLUTE_URI\",\r\n       \"value\": \"${hostname}\"\r\n     }\r\n   ],\r\n   \"links\": [],\r\n   \"workingDirectory\": null,\r\n   \"readonlyRootFilesystem\": null,\r\n   \"image\": \"phpmyadmin\/phpmyadmin:latest\",\r\n   \"command\": [],\r\n   \"user\": null,\r\n   \"dockerLabels\": null,\r\n   \"logConfiguration\": null,\r\n   \"cpu\": 128,\r\n   \"privileged\": null,\r\n   \"memoryReservation\": null\r\n }\r\n]<\/pre>\n<p>When using the <code>template_file<\/code> data resource, you pass variables into it. Within the template file, you can reference them with format <code>${variable_name}<\/code>. Note that in a template you do not use the <code>var<\/code> prefix like you do in other resource definitions.<\/p>\n<p>Now define the configuration for the task definition and service in the <code>main.tf<\/code> file:<\/p>\n<pre class=\"brush:js\">\/\/ service\/main.tf\r\n\/*\r\n * Render task definition from template\r\n *\/\r\ndata \"template_file\" \"task_def\" {\r\n template = \"${file(\"${path.module}\/task-definition.json\")}\"\r\n\r\n vars {\r\n   mysql_host = \"${aws_db_instance.db_instance.address}\"\r\n   hostname   = \"https:\/\/${aws_alb.alb.dns_name}\/\"\r\n }\r\n}\r\n\r\n\/*\r\n * Create task definition\r\n *\/\r\nresource \"aws_ecs_task_definition\" \"td\" {\r\n family                = \"myapp\"\r\n container_definitions = \"${data.template_file.task_def.rendered}\"\r\n network_mode          = \"bridge\"\r\n}\r\n\r\n\/*\r\n * Create ECS Service\r\n *\/\r\nresource \"aws_ecs_service\" \"service\" {\r\n name                               = \"myapp\"\r\n cluster                            = \"${data.terraform_remote_state.shared.ecs_cluster_name}\"\r\n desired_count                      = \"${length(data.terraform_remote_state.shared.aws_zones)}\"\r\n iam_role                           = \"${data.terraform_remote_state.shared.ecsServiceRole_arn}\"\r\n deployment_maximum_percent         = \"200\"\r\n deployment_minimum_healthy_percent = \"50\"\r\n\r\n placement_strategy {\r\n   type  = \"spread\"\r\n   field = \"instanceId\"\r\n }\r\n\r\n load_balancer {\r\n   target_group_arn = \"${aws_alb_target_group.default.arn}\"\r\n   container_name   = \"web\"\r\n   container_port   = \"80\"\r\n }\r\n\r\n task_definition = \"${aws_ecs_task_definition.td.family}:${aws_ecs_task_definition.td.revision}\"\r\n}<\/pre>\n<p>With the service defined and ready, the final step is to create a DNS record on Cloudflare to point to the load balancer so we can demonstrate the ability to interact with multiple service providers in the same configuration. If you do not use Cloudflare, you can replace this part with the DNS configuration of your choice, such as Amazon\u2019s Route 53.<\/p>\n<p>Add the following to <code>main.tf<\/code>.<\/p>\n<pre class=\"brush:js\">\/\/ service\/main.tf\r\n\/*\r\n * Create Cloudflare DNS record\r\n *\/\r\nresource \"cloudflare_record\" \"pmadns\" {\r\n domain  = \"${var.cloudflare_domain}\"\r\n name    = \"pma\"\r\n value   = \"${aws_alb.alb.dns_name}\"\r\n type    = \"CNAME\"\r\n proxied = true\r\n}<\/pre>\n<p>If using Route53, remove the three <code>cloudflare_*<\/code> variable definitions in <code>vars.tf<\/code> and replace them with the following. Use the Zone ID for your Route53 zone for the value of <code>aws_zone_id<\/code>. Use the fully qualified domain name of your system for <code>exthostname<\/code>.<\/p>\n<pre class=\"brush:js\">\/*\r\n * AWS Zone ID in Route 53\r\n * Get the Zone ID for the already existing zone from the AWS Console\r\n *\/\r\nvariable \"aws_zone_id\" {\r\n  default = \"replaceme\"\r\n}\r\n\r\n\/*\r\n * Fully qualified host name to be added to Route 53 zone\r\n *\/\r\nvariable \"exthostname\" {\r\n  default = \"replaceme.example.com\"\r\n}<\/pre>\n<p>If using Route53, remove the definition for <code>url<\/code> in <code>outputs.tf<\/code> and replace it with the following.<\/p>\n<pre class=\"brush:js\">\/*\r\n * Fully Qualified Domain Name\r\n *\/\r\noutput \"fqdn\" {\r\n  value = \"${aws_route53_record.pmadns.fqdn}\"\r\n}<\/pre>\n<p>With the <code>service<\/code> environment set up, running <code>terraform plan<\/code> shows adding 10 new resources for me:<\/p>\n<pre class=\"brush:js\">$ terraform plan\r\nRefreshing Terraform state in-memory prior to plan\u2026\r\n....\r\nPlan: 9 to add, 0 to change, 0 to destroy.<\/pre>\n<p>Make it so.<\/p>\n<pre class=\"brush:js\">$ terraform apply\r\ndata.terraform_remote_state.shared: Refreshing state\u2026\r\n....\r\nApply complete! Resources: 10 added, 0 changed, 0 destroyed.\r\n\r\nThe state of your infrastructure has been saved to the path\r\nbelow. This state is required to modify and destroy your\r\ninfrastructure, so keep it safe. To inspect the complete state\r\nuse the `terraform show` command.\r\n\r\nState path:\r\n\r\nOutputs:\r\n\r\ndbpassword = dbpass1234\r\ndbusername = dbuser\r\nurl = https:\/\/pma.domain.com<\/pre>\n<p>It took about five minutes to complete for me. Creating ALBs and DBs can be a bit slow so be patient. When finished, refreshing the EC2 Container Service clusters view in the console shows our tasks are running:<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image1.png\"><img decoding=\"async\" class=\"aligncenter wp-image-18948\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image1.png\" alt=\"\" width=\"860\" height=\"140\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image1.png 1011w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image1-300x49.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image1-768x125.png 768w\" sizes=\"(max-width: 860px) 100vw, 860px\" \/><\/a><\/p>\n<p>The output shows the URL to the application. Navigating there, I see the login screen and log in using the username and password from the output:<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image5.png\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-18949\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image5.png\" alt=\"\" width=\"423\" height=\"524\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image5.png 423w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image5-242x300.png 242w\" sizes=\"(max-width: 423px) 100vw, 423px\" \/><\/a><\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image4.png\"><img decoding=\"async\" class=\"aligncenter wp-image-18950\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image4.png\" alt=\"\" width=\"860\" height=\"594\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image4.png 1045w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image4-300x207.png 300w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image4-768x531.png 768w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2017\/10\/image4-1024x707.png 1024w\" sizes=\"(max-width: 860px) 100vw, 860px\" \/><\/a><\/p>\n<p>That\u2019s it. We now have a new VPC provisioned, EC2 instances spread across multiple availability zones, an ECS cluster for orchestrating Docker services, a web application running multiple instances load balanced by a dynamic load balancer and interacting with a newly provisioned database.<\/p>\n<h2>Conclusion<\/h2>\n<p>So with a little bit of work up front to define all the configurations, it only takes two commands to bring it all online. And then two commands again to destroy it all. It\u2019s pretty awesome just how powerful Terraform is and how comforting it is to have a reproducible, version controlled, collaborative approach to managing infrastructure.<\/p>\n<p>In the examples above, I hard-coded values for many fields for simplicity where normally I would use variables. Also after figuring out all the configurations needed to get an application online, I\u2019d work on refactoring the configurations into reusable modules. This approach is actually quite similar to any other software development where you build what you need, then refactor and simplify as much as possible.<\/p>\n<p>After getting my first application online with Terraform, I refactored it down to several modules and hosted them in their own Git repo for reuse in all my applications.<\/p>\n<h3>What next?<\/h3>\n<p>If you want to try this out yourself, the <a href=\"https:\/\/github.com\/fillup\/aws-ecs-terraform-example\">examples in this article are available on GitHub<\/a>. Just clone the repo and follow the steps described in the README.<\/p>\n<p>Head over to <a href=\"https:\/\/www.terraform.io\/docs\/\">Terraform and read the official docs<\/a>. Browse the list of Providers available to see how many you use and could benefit from using via Terraform.<\/p>\n<p>You may also be interested in reading <a href=\"http:\/\/www.terraformupandrunning.com\/\">Terraform: Up &amp; Running<\/a> for a deeper and more comprehensive understanding of Terraform. It is a bit dated, but most of the concepts are still applicable.<\/p>\n<p>As mentioned earlier, Terraform is a free and open-source tool, however HashiCorp, the company behind Terraform (any many other great products), has an Enterprise version of the product. Terraform Enterprise connects with your version control system to automate execution of <code>terraform plan<\/code> and even <code>terraform apply<\/code> runs. It also provides more secure variable management and an enhanced team experience through its web interface and Terraform state management.<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td>Published on Web Code Geeks with permission by Phillip Shipley, partner at our <a href=\"http:\/\/www.webcodegeeks.com\/join-us\/wcg\/\" target=\"_blank\" rel=\"noopener\">WCG program<\/a>. See the original article here: <a href=\"https:\/\/blog.codeship.com\/service-resources-for-your-terraformed-docker-environment\/\" target=\"_blank\" rel=\"noopener\">Service Resources for Your Terraformed Docker Environment<\/a><\/p>\n<p>Opinions expressed by Web Code Geeks contributors are their own.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In Part II of this series, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to run on that infrastructure. Service Resources Declaration To keep this tutorial as simple as possible, for my service I am just going to run a web interface for &hellip;<\/p>\n","protected":false},"author":177,"featured_media":10356,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17],"tags":[325,217],"class_list":["post-18946","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-amazon-aws","tag-docker"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Service Resources for Your Terraformed Docker Environment - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"In Part II of this series, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Service Resources for Your Terraformed Docker Environment - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"In Part II of this series, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2017-10-13T09:15:10+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Phillip Shipley\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Phillip Shipley\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/\"},\"author\":{\"name\":\"Phillip Shipley\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/10c965829d30e960ace15d97e738b032\"},\"headline\":\"Service Resources for Your Terraformed Docker Environment\",\"datePublished\":\"2017-10-13T09:15:10+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/\"},\"wordCount\":1234,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"keywords\":[\"Amazon AWS\",\"Docker\"],\"articleSection\":[\"DevOps\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/\",\"name\":\"Service Resources for Your Terraformed Docker Environment - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"datePublished\":\"2017-10-13T09:15:10+00:00\",\"description\":\"In Part II of this series, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"DevOps\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/devops\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Service Resources for Your Terraformed Docker Environment\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/10c965829d30e960ace15d97e738b032\",\"name\":\"Phillip Shipley\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/a5e188393958c4505bb06d9e79ed99a47d1cd42551142f02ef364d8268449ef8?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/a5e188393958c4505bb06d9e79ed99a47d1cd42551142f02ef364d8268449ef8?s=96&d=mm&r=g\",\"caption\":\"Phillip Shipley\"},\"description\":\"Phillip Shipley is the IT Applications Development manager at SIL International, Inc. He spends the majority of his work time developing web applications and has a particular passion for APIs and integration. When he\u2019s not building software, he\u2019s probably building LEGO with his son.\",\"url\":\"https:\/\/www.webcodegeeks.com\/author\/phillip-shipley\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Service Resources for Your Terraformed Docker Environment - Web Code Geeks - 2026","description":"In Part II of this series, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/","og_locale":"en_US","og_type":"article","og_title":"Service Resources for Your Terraformed Docker Environment - Web Code Geeks - 2026","og_description":"In Part II of this series, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to","og_url":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2017-10-13T09:15:10+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","type":"image\/jpeg"}],"author":"Phillip Shipley","twitter_card":"summary_large_image","twitter_creator":"@webcodegeeks","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Phillip Shipley","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/"},"author":{"name":"Phillip Shipley","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/10c965829d30e960ace15d97e738b032"},"headline":"Service Resources for Your Terraformed Docker Environment","datePublished":"2017-10-13T09:15:10+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/"},"wordCount":1234,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","keywords":["Amazon AWS","Docker"],"articleSection":["DevOps"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/","url":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/","name":"Service Resources for Your Terraformed Docker Environment - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","datePublished":"2017-10-13T09:15:10+00:00","description":"In Part II of this series, we defined and applied our shared infrastructure. Today we\u2019ll continue where we left off and define an actual Docker service to","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2016\/01\/docker-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/devops\/service-resources-terraformed-docker-environment\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"DevOps","item":"https:\/\/www.webcodegeeks.com\/category\/devops\/"},{"@type":"ListItem","position":3,"name":"Service Resources for Your Terraformed Docker Environment"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/10c965829d30e960ace15d97e738b032","name":"Phillip Shipley","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/a5e188393958c4505bb06d9e79ed99a47d1cd42551142f02ef364d8268449ef8?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a5e188393958c4505bb06d9e79ed99a47d1cd42551142f02ef364d8268449ef8?s=96&d=mm&r=g","caption":"Phillip Shipley"},"description":"Phillip Shipley is the IT Applications Development manager at SIL International, Inc. He spends the majority of his work time developing web applications and has a particular passion for APIs and integration. When he\u2019s not building software, he\u2019s probably building LEGO with his son.","url":"https:\/\/www.webcodegeeks.com\/author\/phillip-shipley\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/18946","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/177"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=18946"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/18946\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/10356"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=18946"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=18946"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=18946"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}