Skip to main content

Managing and deploying containerized applications

Starting a containerized application

To start a container via the mStudio, follow these steps:

  1. Navigate to the project that you want to create the container in.
  2. Select the "Containers" menu item in the sidebar.
  3. Click the "Create Container" button.
  4. 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.

Starting containers with resource limitations

You can start containers with CPU and memory limits to control the resources that your containerized application can consume. This is useful for ensuring that your containers do not consume more resources than expected, and for optimizing resource allocation across multiple containers.

To start a container with resource limits via the CLI, you can use the --cpus and --memory flags with the mw container run command:

$ mw container run \
--name my-container \
--cpus 0.5 \
--memory 512m \
--env FOO=BAR \
-p 8000:8000/tcp \
my-registry/my-container:latest

The --cpus flag specifies the maximum number of CPUs the container can use (e.g., 0.5 for half a CPU, 2 for two CPUs). The --memory flag specifies the maximum amount of memory the container can use (e.g., 512m for 512 megabytes, 1g for 1 gigabyte).

If you have a Docker Compose compatible file, you can also specify resource limits using the standard Docker Compose syntax:

services:
mycontainer:
image: my-registry/my-container:latest
ports:
- "8000:8000/tcp"
environment:
FOO: BAR
deploy:
resources:
limits:
cpus: '0.5'
memory: 512m

Then deploy the stack using the mw stack deploy command:

$ mw stack deploy

See the documentation on the mw container run command for more information.

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:

To create a new container registry via the mStudio, follow these steps:

  1. Navigate to the project that you want to create the container in.
  2. Select the "Containers" menu item in the sidebar.
  3. Switch to the "Registries" tab.
  4. Click the "Add registry" button.
  5. Configure registry URL and credentials for the registry.

Custom, self-hosted Docker image registry

If no private registry exists or is unavailable for any reason, it is possible to host your own Docker image registry in the mittwald container hosting.

Check this guide for detailed instructions and configuration.

To create a new container registry via the mStudio, follow these steps:

  1. Navigate to the project that you want to create the container in.
  2. Select the "Containers" menu item in the sidebar.
  3. In the open "Container" tab, click the "Create" button.
  4. Configure the container as needed, use library/registry:3 image.
  5. Set up environment variables to your liking; check the guide above for details.

The registry set up in this chapter can be used as private registry, so make sure to make registry reachable, e.g. by creating a subdomain in your project pointing towards custom registry.

A custom registry might end up with a domain like docker.p-XXXXXX.project.space, which can be configured as registry for future container deployments.

Basic auth for self-hosted Docker registry

In order to add basic authentication to our registry we need to prepare credentials which we then deploy into our registry.

First step is to create the credentials file locally:

user@local $ mkdir -p auth
user@local $ docker run \
--entrypoint htpasswd \
httpd:2 -Bbn exampleuser examplepassword > auth/htpasswd

This will create the htpasswd credentials file, which we now have to ship.

To persist the credentials file, we start with creating a new volume for our registry container:

To create a new container volume via the mStudio, follow these steps:

  1. Navigate to the project that you have created the registry in.
  2. Select the "Containers" menu item in the sidebar, then select the registry container.
  3. In the open "Container" tab, navigate to "Volumes" tab.
  4. In the "Volumes" tab, click "Create".
  5. Configure new volume mount point to /auth and click "Save".

After that, we reconfigure our registry to use new basic auth credentials.

To finalize self-hosted registry container configuration, follow these steps:

  1. Navigate to the project that you have created the registry in.
  2. Select the "Containers" menu item in the sidebar, then select the registry container.
  3. In the open "Container" tab, navigate to "Environment Variables" tab.
  4. Use the "Add" button to create environment variables as follows:
REGISTRY_AUTH=htpasswd
REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm
REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd

(Re)create the container with volumes and environment variables set. Then we ship our credentials file, overriding default user credentials ( to be found in logs if needed ).

user@local $ mw container cp -r auth/ example-registry:/

As final step, create a subdomain in your project and point it to registry container.

To add and link a subdomain in your project, follow these steps:

  1. Navigate to the project that you have created the registry in.
  2. Select the "Domains" menu item in the sidebar, then click "Add" -> "Subdomain".
  3. As subdomain, for example use docker.p-XXXXXX.project.space.
  4. Select the registry container as target and click "Create".

Now, after domain has been set up, we can start testing. As we have configured basic auth, we should check our registry to actually use it. Build and tag an arbitrary Docker image, then push to new registry:

user@local $ cd path/to/your/project
user@local $ docker build -t <Your Image> .
user@local $ docker tag <Your Image>:latest docker.p-XXXXXX.project.space/<Your Image>
user@local $ docker push docker.p-XXXXXX.project.space/<Your Image>:latest

Your push must be rejected due to missing basic auth credentials:

user@local $ docker push docker.p-XXXXXX.project.space/<Your Image>:latest
...
push access denied, repository does not exist or may require authorization: authorization failed: no basic auth credentials

If it worked without errors, your setup is incomplete and no authentication is active! Now log in:

user@local $ docker login docker.p-XXXXXX.project.space

Retry docker push, which now works after successful login! Congratulations, you have successfully created your own private, self-hosted Docker registry. Depending on your project structure and use case, one generic Docker registry in a separate project, or one registry per project can be created and used.

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:

  1. 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.
  2. 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.

To update the container image of an existing container, use the mw container update command:

$ mw container update \
--image my-registry/my-container:v1.0.1 \
my-container

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/v2/stacks/{stackId}/services/{serviceId}/actions/pull/ operation or the mw container recreate --pull CLI command 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:

  1. 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.
  2. 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"
]
}
}
}
See full request reference at: PUT/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": {}
}
}
See full request reference at: PUT/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"
]
}
}
}
See full request reference at: PUT/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.

To connect an ingress resource to a container, use the mw domain virtualhost create command with the --path-to-container flag:

mw domain virtualhost create --hostname domain.example --path-to-container /:[container-uid]:80/tcp

The --path-to-container flag should contain three values, separated by colons:

  1. The URL path that should be mapped to the container.
  2. The container UID.
  3. The port (in the format portnumber/protocol, so for example 80/tcp) that the container is listening on.