Creating Docker Images
In this part, you’ll create your first Pulumi resource. Resources in Pulumi are the basic building blocks of your infrastructure, whether that’s a database instance or a compute instance or a specific storage bucket. In Pulumi, resource providers manage your resources. You can group those resources to abstract them (such as a group of compute instances that all have the same configuration and implementation) via component resources.
In this case, the resources you’re provisioning will be Docker containers and images that you build locally with Pulumi infrastructure as code. The resource provider is Docker, and you’ll be using Node.jsNode.jsPythonGo.NET.NET.NETJava as your language host, or the executor that compiles the code that you write and interprets it for Pulumi.
Verify your application#
Let’s explore the app you’ll be creating. In a browser, open the pulumi/tutorial-pulumi-fundamentals repository on GitHub. In this repository, you’ll find a frontend, backend, and data directory. Each directory contains a Dockerfile that builds the image for that portion of the application.
Let’s examine the backend Dockerfile in backend/Dockerfile:
FROM node:14
# Create app directoryWORKDIR /usr/src/app
COPY ./src/package*.json ./
RUN npm installCOPY ./src .RUN npm buildEXPOSE 3000
CMD [ "npm", "start" ]This Dockerfile copies the REST backend into the Docker filesystem, installs the dependencies, and builds the image. Note that port 3000 must be open on your host machine.
Pull a Docker Image with Pulumi#
Before you start writing a Pulumi program, you need to install a resource provider. In this case, you’ll need the @pulumi/docker provider for Node.js. Let’s install the provider now:
npm install @pulumi/dockerBefore you start writing a Pulumi program, you need to install a resource provider. In this case, you’ll need the pulumi_docker provider for Python. It’s always good practice for Python to work inside a virtual environment, or venv, so let’s activate our venv and use pip to install the provider along with the main Pulumi package:
cd ../source venv/bin/activatepip3 install pulumi_dockerYou should see output showing the provider package being installed, just like for any Python package install. Add the package to the requirements.txt file by adding pulumi_docker on a new line at the end of the file.
Back inside your Pulumi program, you’re ready to pull your first Docker image. Remember that a Pulumi program is the code that defines the desired state of your infrastructure using a general-purpose programming language. In this case, you’re using TypeScriptJavaScriptPythonGoC#F#VBJavaYAMLHCLShellPowerShell, so the program file is index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yaml. In index.tsindex.js__main__.pymain.goProgram.csProgram.fsProgram.vbApp.javaPulumi.yaml, use any editor to add the following code:
import * as pulumi from "@pulumi/pulumi";import * as docker from "@pulumi/docker";
const stack = pulumi.getStack();
// Pull the backend imageconst backendImageName = "backend";const backend = new docker.RemoteImage(`${backendImageName}Image`, { name: "pulumi/tutorial-pulumi-fundamentals-backend:latest",});import pulumiimport pulumi_docker as docker
stack = pulumi.get_stack()
# Pull the backend imagebackend_image_name = "backend"backend = docker.RemoteImage( f"{backend_image_name}_image", name="pulumi/tutorial-pulumi-fundamentals-backend:latest",)In this file, we import the main Pulumi package and the Docker provider. Then, we figure out which stack we’re operating against, and populate the stack variable for later use. When we pull our backend image, we give it a name in our stack as “backendImage” before passing some arguments to the Docker provider. The Docker provider uses the name argument to pull a remote image for us to use.
Notice that we’re mixing in some language constructs in here like string interpolation. With Pulumi, we have access to the full language ecosystem, including built-ins and third-party libraries. Pulumi also has typing support, so you can use the tools in your favorite IDE, like auto-completion, to verify that you’re using the correct types for any inputs you’re using.
In this file, we import the main Pulumi package and the Docker provider. Then, we figure out which stack we’re operating against, and populate the stack variable for later use. When we pull our backend image, we give it a name in our stack as “backend_image” before passing some arguments to the Docker provider. The Docker provider uses the name argument to pull a remote image for us to use.
Notice that we’re mixing in some Python constructs in here like f-strings (string interpolation). With Pulumi, we have access to the full language ecosystem, including third-party libraries. Pulumi also has typing support, so you can use the tools in your favorite IDE, like auto-completion, to verify that you’re using the correct types for any inputs you’re using.
Run pulumi up.
Pulumi should pull your Docker image. First, though, it gives you a preview of the changes you’ll be making to the stack and asks if the changes appear okay to you. You’ll need to reply “yes” to the prompt to actually pull the image. After the command finishes, you will see your image if you run the command docker images or docker image ls (depending on your preference).
Let’s dig a bit deeper into the code and explore the various Pulumi concepts. Every resource has inputs and outputs. Inputs are values that are provided to the resource. Outputs are the resource’s properties. Note that Pulumi can’t know the output until the resource has completed provisioning as some of those outputs are provided by the provider after everything has loaded, booted, or otherwise has come online. More on outputs later.
In our case here, the Docker RemoteImage resource takes the following inputs:
- an unnamed string: a name for the resource we are creating
name: the name of the remote image to pull down
- an unnamed string: a name for the resource we are creating
name: the name of the remote image to pull down
Now that you’ve provisioned the first piece of infrastructure, you can continue adding the other components of the application.
Add the frontend client and MongoDB#
The application we’re building includes a frontend client and a MongoDB database. You’ll add them both to the program next. Add the following code after the previous fragment:
// Pull the frontend imageconst frontendImageName = "frontend";const frontend = new docker.RemoteImage(`${frontendImageName}Image`, { name: "pulumi/tutorial-pulumi-fundamentals-frontend:latest",});
// Pull the MongoDB imageconst mongoImage = new docker.RemoteImage("mongoImage", { name: "pulumi/tutorial-pulumi-fundamentals-database:latest",});# Pull the frontend imagefrontend_image_name = "frontend"frontend = docker.RemoteImage( f"{frontend_image_name}_image", name="pulumi/tutorial-pulumi-fundamentals-frontend:latest",)
# Pull the MongoDB imagemongo_image = docker.RemoteImage( "mongo_image", name="pulumi/tutorial-pulumi-fundamentals-database:latest")This pulls the frontend client and the populated database images in the same way you pulled the backend image earlier.
Compare your program now to the completed program below before moving forward:
import * as pulumi from "@pulumi/pulumi";import * as docker from "@pulumi/docker";
const stack = pulumi.getStack();
// Pull the backend imageconst backendImageName = "backend";const backend = new docker.RemoteImage(`${backendImageName}Image`, { name: "pulumi/tutorial-pulumi-fundamentals-backend:latest",});
// Pull the frontend imageconst frontendImageName = "frontend";const frontend = new docker.RemoteImage(`${frontendImageName}Image`, { name: "pulumi/tutorial-pulumi-fundamentals-frontend:latest",});
// Pull the MongoDB imageconst mongoImage = new docker.RemoteImage("mongoImage", { name: "pulumi/tutorial-pulumi-fundamentals-database:latest",});import pulumiimport pulumi_docker as docker
stack = pulumi.get_stack()
# Pull the backend imagebackend_image_name = "backend"backend = docker.RemoteImage( f"{backend_image_name}_image", name="pulumi/tutorial-pulumi-fundamentals-backend:latest",)
# Pull the frontend imagefrontend_image_name = "frontend"frontend = docker.RemoteImage( f"{frontend_image_name}_image", name="pulumi/tutorial-pulumi-fundamentals-frontend:latest",)
# Pull the MongoDB imagemongo_image = docker.RemoteImage( "mongo_image", name="pulumi/tutorial-pulumi-fundamentals-database:latest")If your code looks the same, great! Otherwise, update yours to match this code.
Now, run pulumi up to pull all of the images that we’ll need.
From here, we can move on to configuring and provisioning our containers.