Skip to main content

Search the Dev Center

Search tutorials, templates, examples, and more.

Container Service on Azure

The Azure Container Service template scaffolds a Pulumi project that deploys a containerized service to Azure. The architecture includes an Azure Container Registry for the container image and Azure Container Instances (ACI) for serverless container execution. The template ships with placeholder app content so the project deploys end to end out of the box.

Architecture diagram of the Azure Container Service template Azure Azure ContainerService Container Group Container Registry

Before you begin#

For this walkthrough, you'll need:

Create a new project#

Create a new project from the template:

Terminal window
mkdir my-container-service; cd my-container-service
pulumi new container-azure-typescript

The pulumi new command walks you through creating and configuring the new project and stack. A stack is an instance of a project, and you can have many of them (dev, staging, prod), each with its own configuration settings. Depending on the language, you may also be asked for your preferred package manager or toolchain.

This template’s settings include:

KeyDescriptionDefault
azure-native:locationThe Azure region to deploy intoWestUS
appPathThe path to the container application to deployapp
imageNameThe name to give the container imagemy-app
imageTagThe tag to give the container imagelatest
containerPortThe port to expose on the container80
cpuThe number of CPU cores to allocate on the container1
memoryThe amount of memory, in GB, to allocate on the container2
You can change values like these later with pulumi config set :
Terminal window
pulumi config set azure-native:location <value>
pulumi config set appPath <value>
pulumi config set imageName <value>
pulumi config set imageTag <value>
pulumi config set containerPort <value>
pulumi config set cpu <value>
pulumi config set memory <value>

Review the code#

When setup completes, you’ll see a few files:

  • Pulumi.yaml contains the project name and the language it’s written in.
  • Pulumi.dev.yaml contains the dev stack’s settings, including the values you just entered.
  • index.ts contains the program code that declares your cloud resources.
  • app holds a small web app that answers with a “Hello, world!” message, and the Dockerfile that builds it.

Take a moment to open them up and have a look. Here’s what’s in index.ts:

index.ts
import * as pulumi from "@pulumi/pulumi";
import * as resources from "@pulumi/azure-native/resources";
import * as containerregistry from "@pulumi/azure-native/containerregistry";
import * as containerinstance from "@pulumi/azure-native/containerinstance";
import * as random from "@pulumi/random";
import * as dockerbuild from "@pulumi/docker-build";
// Import the program's configuration settings.
const config = new pulumi.Config();
const appPath = config.get("appPath") || "./app";
const imageName = config.get("imageName") || "my-app";
const imageTag = config.get("imageTag") || "latest";
const containerPort = config.getNumber("containerPort") || 80;
const cpu = config.getNumber("cpu") || 1;
const memory = config.getNumber("memory") || 2;
// Create a resource group for the container registry.
const resourceGroup = new resources.ResourceGroup("resource-group");
// Create a container registry.
const registry = new containerregistry.Registry("registry", {
resourceGroupName: resourceGroup.name,
adminUserEnabled: true,
sku: {
name: containerregistry.SkuName.Basic,
},
});
// Fetch login credentials for the registry.
const credentials = containerregistry.listRegistryCredentialsOutput({
resourceGroupName: resourceGroup.name,
registryName: registry.name,
}).apply(creds => {
return {
username: creds.username!,
password: creds.passwords![0].value!,
};
});
// Create a container image for the service.
const image = new dockerbuild.Image("image", {
tags: [pulumi.interpolate`${registry.loginServer}/${imageName}:${imageTag}`],
context: {
location: appPath,
},
platforms: ["linux/amd64"],
push: true,
registries: [{
address: registry.loginServer,
username: credentials.username,
password: credentials.password,
}],
});
// Use a random string to give the service a unique DNS name.
const dnsName = new random.RandomString("dns-name", {
length: 8,
special: false,
}).result.apply(result => `${imageName}-${result.toLowerCase()}`);
// Create a container group for the service that makes it publicly accessible.
const containerGroup = new containerinstance.ContainerGroup("container-group", {
resourceGroupName: resourceGroup.name,
osType: "linux",
restartPolicy: "always",
imageRegistryCredentials: [
{
server: registry.loginServer,
username: credentials.username,
password: credentials.password,
},
],
containers: [
{
name: imageName,
image: image.ref,
ports: [
{
port: containerPort,
protocol: "tcp",
},
],
environmentVariables: [
{
name: "PORT",
value: containerPort.toString(),
},
],
resources: {
requests: {
cpu: cpu,
memoryInGB: memory,
},
},
},
],
ipAddress: {
type: containerinstance.ContainerGroupIpAddressType.Public,
dnsNameLabel: dnsName,
ports: [
{
port: containerPort,
protocol: "tcp",
},
],
},
});
// Export the service's IP address, hostname, and fully-qualified URL.
export const hostname = containerGroup.ipAddress.apply(addr => addr!.fqdn!);
export const ip = containerGroup.ipAddress.apply(addr => addr!.ip!);
export const url = containerGroup.ipAddress.apply(addr => `http://${addr!.fqdn!}:${containerPort}`);

Deploy the project#

Deploy with pulumi up. It starts with a preview of what it’s about to create and asks before it changes anything. Choose yes to deploy.

Pulumi builds the container image with Docker on your machine, so Docker needs to be running.

Terminal window
pulumi up

Pulumi adds a random suffix to most resource names, so the resource group resource-group becomes something like resource-group3f8a2c1d. This is autonaming. It lets you deploy the same program to more than one stack, and lets Pulumi create a replacement before it deletes the old resource, without names colliding.

When it finishes, Pulumi prints the stack’s outputs, values it only knows once the resources exist:

hostname

The hostname of the container group.

ip

The public IP address of the container group.

url

The HTTP URL of the container group.

Read one any time with pulumi stack output <name>, or pass it to another command. This opens the app in your browser:

Terminal window
open $(pulumi stack output url)

To check it from the terminal, call the app with curl:

Terminal window
curl $(pulumi stack output url)
{"message":"Hello, world!"}
Need help?

If pulumi up asks you to run az login, or says your sign-in has expired, sign in again:

Terminal window
az login

Still stuck? Let us know.

Customize the project#

Projects created with this template expose the following configuration settings:

appPath

The path to the folder containing the application and Dockerfile. Defaults to app, which contains a “Hello world” example.

containerPort

The port to expose on the container. Defaults to 80.

cpu

The number of CPU cores to allocate on the container. Defaults to 1.

memory

The amount of memory, in GB, to allocate on the container. Defaults to 2.

imageName

The name of the container image to be published to Azure Container Registry. Defaults to my-app.

imageTag

The tag applied to published container images. Defaults to latest.

All of these settings are optional and may be adjusted either by editing the stack configuration file directly (by default, Pulumi.dev.yaml) or by changing their values with pulumi config set.

For example, move the app to port 8080:

Terminal window
pulumi config set containerPort 8080
pulumi up

The program passes the port to the app and opens it on the container group, so the url output now ends in :8080. Call it again to check:

Terminal window
curl $(pulumi stack output url)
{"message":"Hello, world!"}

Cleaning up#

You can cleanly destroy the stack and all of its infrastructure with pulumi destroy:

Terminal window
pulumi destroy

Learn more#

Related

The infrastructure as code platform for any cloud.