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
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 directory
WORKDIR /usr/src/app
COPY ./src/package*.json ./
RUN npm install
COPY ./src .
RUN npm build
EXPOSE 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/docker
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 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/activate
pip3 install pulumi_docker
You 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 index.js
index.ts
__main__.py
main.go
Program.cs
Program.fs
Program.vb
App.java
Pulumi.yaml
index.js
index.ts
__main__.py
main.go
Program.cs
Program.fs
Program.vb
App.java
Pulumi.yaml
import * as pulumi from "@pulumi/pulumi";
import * as docker from "@pulumi/docker";
const stack = pulumi.getStack();
// Pull the backend image
const backendImageName = "backend";
const backend = new docker.RemoteImage(`${backendImageName}Image`, {
name: "pulumi/tutorial-pulumi-fundamentals-backend:latest",
});
import pulumi
import pulumi_docker as docker
stack = pulumi.get_stack()
# Pull the backend image
backend_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 image
const frontendImageName = "frontend";
const frontend = new docker.RemoteImage(`${frontendImageName}Image`, {
name: "pulumi/tutorial-pulumi-fundamentals-frontend:latest",
});
// Pull the MongoDB image
const mongoImage = new docker.RemoteImage("mongoImage", {
name: "pulumi/tutorial-pulumi-fundamentals-database:latest",
});
# Pull the frontend image
frontend_image_name = "frontend"
frontend = docker.RemoteImage(
f"{frontend_image_name}_image",
name="pulumi/tutorial-pulumi-fundamentals-frontend:latest",
)
# Pull the MongoDB image
mongo_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 image
const backendImageName = "backend";
const backend = new docker.RemoteImage(`${backendImageName}Image`, {
name: "pulumi/tutorial-pulumi-fundamentals-backend:latest",
});
// Pull the frontend image
const frontendImageName = "frontend";
const frontend = new docker.RemoteImage(`${frontendImageName}Image`, {
name: "pulumi/tutorial-pulumi-fundamentals-frontend:latest",
});
// Pull the MongoDB image
const mongoImage = new docker.RemoteImage("mongoImage", {
name: "pulumi/tutorial-pulumi-fundamentals-database:latest",
});
import pulumi
import pulumi_docker as docker
stack = pulumi.get_stack()
# Pull the backend image
backend_image_name = "backend"
backend = docker.RemoteImage(
f"{backend_image_name}_image",
name="pulumi/tutorial-pulumi-fundamentals-backend:latest",
)
# Pull the frontend image
frontend_image_name = "frontend"
frontend = docker.RemoteImage(
f"{frontend_image_name}_image",
name="pulumi/tutorial-pulumi-fundamentals-frontend:latest",
)
# Pull the MongoDB image
mongo_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.
pulumi up
in stages like this
to create your infrastructure. You can write the entire program and then run it.
We’re only doing a step-by-step process here to make learning easier.From here, we can move on to configuring and provisioning our containers.