With the launch of our new AlmaLinux 9 servers, we're finally able to support containerized applications (aka "Docker apps") via podman, a rootless container system which is compatible with Docker.
We'll be launching a set of new installers for a variety of containerized applications in the near future, but I'm writing this guide to help you get started running your own containers as custom applications.
Overview
We'll have three examples, all using Ghost as the containerized application:
- A basic example for a self-contained app that maps the web port of the container app to a proxy port app on our end.
- An advanced example that runs the app in a container but uses storage and database services outside of the container.
- A third example that moves all of the config of the advanced example into a
compose.yml
file to let you start the app with a short command instead a long command with a lot of different options.
All three examples use a "Nginx Proxy Port" application as the starting point. Proxy port apps provide 2 things:
- A port assignment for the app's web service. When you add the app to a site, we generate Nginx configuration upstream to proxy requests for your site to your app's port.
- A directory to store the files for the app, located under
~/apps
in the app's shell user's home directory.
Example 1: self-contained app
In this example, we'll use podman to spin up a development instance of Ghost. This instance will run on a sqlite datbaase entirely within the container, with the default Ghost port 2368 mapped to the proxy port app's assigned port.
Create a new Nginx Proxy Port app (see Adding an Application) and make a note of the app's name and port assignment.
Add the app to a site (see Adding Sites) and make a note of the site domain.
Log into SSH as your app's shell user (see SSH Access) and execute the following command, with the following changes:
Replace "name-of-app" with your app's name
Replace "mydomain.com" with your site domain
Replace "11111" with your app's port assignment
podman run -d --name name-of-app \
-e NODE_ENV=development \
-e url=https://mydomain.com \
-p 11111:2368 \
ghost
(Note: if you see warning messages regarding cgroupv2 and systemd, then run the loginctl enable-linger NNNN
command shown in the error message.)
After entering the command, podman will download the container and start the app listening on your proxy port app's assigned port. You should then be able to view the app using the site you created in step 2 above.
To stop the app, run:
podman stop name-of-app
To start it again in the future:
podman start name-of-app
Example 2: containerized app with external database and storage
In this example, we'll use podman to spin up a production instance of Ghost. This instance will run Ghost inside of the container, but the app will use an Opalstack managed database and will store its content in the app directory.
Create a new Nginx Proxy Port app (see Adding an Application) and make a note of the app's name and port assignment.
Add the app to a site (see Adding Sites) and make a note of the site domain.
Create a new MySQL database and user (see [MariaDB database], note that on our AlmaLinux servers MariaDB is actually MySQL but the control panel does not yet reflect this).
Log into SSH as your app's shell user (see SSH Access) and execute the following commands, with the following changes:
Replace "name-of-app" with your app's name
Replace "shell-user" with the name of your app's shell user
Replace "mydomain.com" with your site domain
Replace "11111" with your app's port assignment
Replace "mydbname" with the name of your database.
Replace "mydbuser" with the name of your database user.
Replace "mydbpassword" with the name of your database user password.
mkdir ~/apps/name-of-app/content
podman run -d \
--name name-of-app \
--network host \
-v /home/shell-user/apps/name-of-app/content:/var/lib/ghost/content \
-e server__port=11111 \
-e NODE_ENV=production \
-e url=https://mydomain.com/ \
-e database__client=mysql \
-e database__connection__host=localhost \
-e database__connection__database=mydbname \
-e database__connection__user=mydbuser \
-e database__connection__password=mydbpassword \
ghost:latest
You may have noticed that there are several more options in this example compare to the previous one. The key differences are:
- The
--network
option connects the container directly to the server's network. This allows the app inside of the container to connect directly to the database service running on the server.
- The
-v
option tells the container to map its internal /var/lib/ghost/content
directory to a subdirectory 'content' in your app directory.
- The
-e server__port...
option tells Ghost to listen on your app's assigned port instead of the default port 2368. This differs from the previous example which used the -p
option to map an internal port on the container to a port on the host network. In this case, the port mapping is not needed because the container is connected directly to the host network.
- The various
-e database...
options configure Ghost to use your MySQL database from step 3. The previous example did not need these options because it used an internal database.
After entering the command, podman will download the container and start the app listening on your proxy port app's assigned port. You should then be able to view the app using the site you created in step 2 above.
To stop the app, run:
podman stop name-of-app
To start it again in the future:
podman start name-of-app
Example 3: podman-compose
The previous example had several command line options to deal with. This next example simplifies that by moving the options into a podman-compose configuration file:
Create a new Nginx Proxy Port app (see Adding an Application) and make a note of the app's name and port assignment.
Add the app to a site (see Adding Sites) and make a note of the site domain.
Create a new MySQL database and user (see [MariaDB database], note that on our AlmaLinux servers MariaDB is actually MySQL but the control panel does not yet reflect this).
Log into SSH as your app's shell user (see SSH Access) and create a file named compose.yml
in the app directory with the following contents, replacing the example values as follows:
Replace "name-of-app" with your app's name
Replace "shell-user" with the name of your app's shell user
Replace "mydomain.com" with your site domain
Replace "11111" with your app's port assignment
Replace "mydbname" with the name of your database.
Replace "mydbuser" with the name of your database user.
Replace "mydbpassword" with the name of your database user password.
services:
ghostpod:
image: ghost:latest
container_name: name-of-app
network_mode: host
environment:
- url=https://mydomain.com/
- NODE_ENV=production
- server__port=11111
- database__client=mysql
- database__connection__host=localhost
- database__connection__database=mydbname
- database__connection__user=mydbuser
- database__connection__password=mydbpassword
volumes:
- /home/shell-user/apps/name-of-app/content:/var/lib/ghost/content
restart: unless-stopped
After you've created the compose.yml
file, run the following command to start the app:
podman-compose -f ~/apps/name-of-app/compose.yml up -d
After entering the command, podman will download the container and start the app with all of the configuration from your compose.yml
. You should then be able to view the app using the site you created in step 2 above.
To stop the app, run:
podman-compose -f ~/apps/name-of-app/compose.yml down
Summary
That's it! We've demonstrated 3 different ways to run containers on Opalstack's AlmaLinux 9 servers:
- As a completely self-contained app with a port mapping
- As a complex app using an external database and storage
- As a complex app using an external database and storage, with configuration managed with podman-compose
The concepts presented here can be adapted to run almost any containerized application. Note, however, that the various -e
options (environemt
in the compose example) are specific to Ghost. When running something else, be sure to check the documentation for your software and its container project for the options you may need to use.
What are you planning to run with podman on Opalstack? Let us know in the comments, and if you get something cool up and running feel free to post your own tutorial in the forum!