Managing and deploying containerized applications
Starting a containerized application
- mStudio UI
- CLI
- Terraform
- API
To start a container via the mStudio, follow these steps:
- Navigate to the project that you want to create the container in.
- Select the "Containers" menu item in the sidebar.
- Click the "Create Container" button.
- In the installation wizard, select the container image that should be used to start your application and complete the wizard, by providing the desired configuration regarding environment variables, volumes and network ports.
The internal DNS name of your container will be derived from the name of the container. For example, if you create a container named My container
, the internal DNS name will be my-container
. You can observe the internal DNS name in the UI after the container has been created.
Managing containers via the CLI is currently not supported. Please upvote the respective feature request.
To deploy a container using Terraform, you can use the following example:
locals {
nginx_port = 80
}
resource "mittwald_container_stack" "nginx" {
project_id = mittwald_project.example.id
default_stack = true
containers = {
nginx = {
description = "Example web server"
image = "nginx:1.27.4"
// entrypoint and command *must* be specified, even if they are the defaults.
// To dynamically determine the default entrypoint and command, use the
// `mittwald_container_image` data source.
entrypoint = ["/docker-entrypoint.sh"]
command = ["nginx", "-g", "daemon off;"]
// environment = {
// FOO = "bar"
// }
ports = [
{
container_port = 80
public_port = local.nginx_port
protocol = "tcp"
}
]
volumes = [
{
project_path = "/html"
mount_path = "/usr/share/nginx/html"
}
]
}
}
volumes = {
example = {}
}
}
To learn how to deploy a container via the API, read the article "Starting a container".
Using private registries
If your container image should be loaded from a private registry, you first need to define this registry for the respective project. You can create a registry as follows:
- mStudio UI
- CLI
- Terraform
- API
To create a new container registry via the mStudio, follow these steps:
- Navigate to the project that you want to create the container in.
- Select the "Containers" menu item in the sidebar.
- Switch to the "Registries" tab.
- Click the "Add registry" button.
- Configure registry URL and credentials for the registry.
Managing container registries via the CLI is currently not supported. Please upvote the respective feature request.
To define a container registry using Terraform, you can use the following example:
variable "registry_credentials" {
sensitive = true
type = object({
username = string
password = string
password_version = number
})
}
resource "mittwald_container_registry" "custom_registry" {
project_id = "<project-id>"
description = "My custom registry"
uri = "registry.company.example"
credentials = {
username = var.registry_credentials.username
// password_wo is a write-only attribute, which will not be persisted
// in the state file. You will need to increase password_wo_version
// whenever the password changes.
password_wo = var.registry_credentials.password
password_wo_version = var.registry_credentials.password_version
}
}
When applying your configuration, you can either define the registry credentials in a .tfvars
file or use the -var
flag to pass them in. The password is a write-only attribute, which will not be persisted in the state file. You will need to increase the password_wo_version
whenever the password changes.
$ terraform apply -var registry_credentials='{"username": "username", "password": "password", "password_version": 1}'
To learn how to create or modify a container registry via the API, read the article "Starting a container".
Deployment strategies
When using containers for deployment, you will typically roll out new versions of your application by creating a new container image and deploying it to the same container stack. This is a common practice in containerized environments, as it allows for easy versioning and rollback.
There are two variants of this approach:
- Immutable deployment: Each new version of the application is deployed to a new container image, and the old image is kept for rollback purposes. This is the most common approach in containerized environments.
- Mutable deployment: The same container image (in the easiest case, the
latest
tag) is used for all versions of the application, and each new version replaces the old one.
Both of these strategies can be implemented using mStudio containers. The following sections describe how to implement them.
Pushing a new tag for each release
Using this strategy, you will create a new container image for each release of your application. Using docker
commands, this might look like this (the v1.0.1
tag is just an example for a tag that might increase its version number with each release):
docker build -t my-registry/my-container:v1.0.1 .
docker push my-registry/my-container:v1.0.1
After the image is built, you can deploy it to your container stack.
- CLI
- Terraform
- API
Managing containers via the CLI is currently not supported. Please upvote the respective feature request.
When you are using Terraform to deploy your containerized application, you can use a Terraform variable to set the image tag. This allows you to easily change the image tag for each release:
variable "image_tag" {
type = string
}
resource "mittwald_container_stack" "my_application" {
project_id = mittwald_project.example.id
default_stack = true
containers = {
mycontainer = {
image = "my-registry/my-container:${var.image_tag}"
// ...
}
}
}
When running terraform apply
, you can set the image_tag
variable to the desired version:
terraform apply -var="image_tag=v1.0.1"
Using the API, you can use the PATCH/
operation to replace the image of the container stack with the new image.
PATCH /v2/stacks/{stackId} HTTP/1.1
Host: api.mittwald.de
Content-Type: application/json
{
"services": {
"mycontainer": {
"image": "my-registry/my-container:v1.0.1"
}
}
}
/v2/stacks/{stackId}/
After updating the image, the container will need to be explicitly recreated to apply the changes. This can be done using the POST/
operation.
Updating a mutable tag
Using this strategy, you will typically update the same container image tag over and over again. Using docker
commands, this might look like this:
docker build -t my-registry/my-container:latest .
docker push my-registry/my-container:latest
By default, container images are not automatically updated when the image tag is not changed. You can use the POST/
operation to pull the latest image for the container stack. This will update the image tag to the latest version.
Using volumes
To manage persistent data in your containerized application, you can use volumes. Volumes are a way to store data outside of the container, so that it is not lost when the container is stopped or removed.
You can use two different types of volumes:
- The project volume is a volume that is created for each project and can be accessed by all containers and all managed apps in that project. This is useful for sharing data between containers and apps.
- You can also declare volumes as part of a stack. These are bound to the container stack and are not accessible from other stacks. This is useful for storing data that is only needed by a specific container stack.
Using the project volume
To use the project volume, use a volume declaration like this:
PUT /v2/stacks/{stackId} HTTP/1.1
Host: api.mittwald.de
Content-Type: application/json
{
"services": {
"mycontainer": {
"image": "my-registry/my-container:v1.0.1",
"volumes": [
"/home/p-XXXXX/html:/var/www"
]
}
}
}
/v2/stacks/{stackId}/
Declaring volumes in stacks
To declare a volume within the stack, use a volume declaration like this:
PUT /v2/stacks/{stackId} HTTP/1.1
Host: api.mittwald.de
Content-Type: application/json
{
"services": {
"mycontainer": {
"image": "my-registry/my-container:v1.0.1",
"volumes": [
"myvolume:/var/www"
]
}
},
"volumes": {
"myvolume": {}
}
}
/v2/stacks/{stackId}/
Backup & recovery
All volumes (both project volumes and stack volumes) are backed up automatically as part of the project backup. This means that you can restore your data in case of a failure or data loss.
However, a simple filesystem backup might not be sufficient for all application workloads. For example, if your application is using a database, you should ensure that the database is in a consistent state before taking a backup. This can be done by using the database's built-in backup functionality or by using a third-party backup solution.
Network connectivity between containers and apps
Managed applications and containers are connected to the same network. This means that you can access managed applications from your containers and vice versa. The hostname of the container is the map key of the container in the stack; if you created the container via the GUI, the hostname is derived from the name of the container. For example, if you create a container named My container
, the internal DNS name will be my-container
. You can observe the internal DNS name in the UI after the container has been created.
To make a container port accessible from within your project, you can use the ports
property in the container declaration. This will create a port mapping between the container port and service that is exposed within your hosting environment. For example, to make port 80 of the container accessible for other workloads running in the same project (be it other containers, or managed apps), you can use the following declaration:
PUT /v2/stacks/{stackId} HTTP/1.1
Host: api.mittwald.de
Content-Type: application/json
{
"services": {
"mycontainer": {
"image": "my-registry/my-container:latest",
"ports": [
"80:80/tcp"
]
}
}
}
/v2/stacks/{stackId}/
Making containers accessible from the internet via HTTP
In order to make a container HTTP port accessible from the internet, you need to define an Ingress resource which maps to the given container.
- CLI
- Terraform
- API
Managing containers via the CLI is currently not supported. Please upvote the respective feature request.
Use a mittwald_virtualhost
resource to create an Ingress resource for your container. The following example shows how to create an Ingress resource for a container:
locals {
nginx_port = 80
}
resource "mittwald_container_stack" "nginx" {
project_id = mittwald_project.example.id
default_stack = true
containers = {
nginx = {
description = "Example web server"
image = "nginx:1.27.4"
ports = [
{
container_port = 80
public_port = local.nginx_port
protocol = "tcp"
}
]
/* ... */
}
}
}
resource "mittwald_virtualhost" "nginx" {
hostname = "your-domain.example"
project_id = mittwald_project.test.id
paths = {
"/" = {
container = {
container_id = mittwald_container_stack.nginx.containers.nginx.id
port = "${local.nginx_port}/tcp"
}
}
}
}
POST /v2/ingresses HTTP/1.1
Host: api.mittwald.de
Content-Type: application/json
{
"hostname": "some-hostname.example",
"projectId": "<project-id>",
"paths": [
{
"path": "/",
"target": {
"container": {
"id": "<container-id>",
"portProtocol": "80/tcp"
}
}
}
]
}
/v2/ingresses/