Tutorial: Managing Podman Containers Restart with Systemd
This tutorial will guide you through setting up a script that manages Podman containers using systemd. The script ensures that containers are restarted automatically based on specified policies and cleans up systemd services when containers are removed. Additionally, it handles forcibly removing containers and removing images with their associated containers.
Prerequisites
- Podman installed on your system
- Systemd support in your user environment
- Basic knowledge of bash scripting
Script Overview
The script performs the following tasks:
- Checks for restart policy flags in the Podman command.
- Extracts the container name from the command-line arguments.
- Runs the original Podman command with the provided arguments.
- Creates or updates a systemd service based on the restart policy.
- Cleans up the systemd service when containers are removed.
- Handles forcibly removing containers and associated systemd services.
- Handles removing images and cleans up services for containers using the image.
The Script
Step 1: Create the Script
-
Create the Script File
Create a new file for the script, for example,
manage_podman.sh.nano ~/manage_podman.sh -
Add the Script Content
Copy and paste the following script into
manage_podman.sh:
#!/bin/bash
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
# Function to clean up systemd service
cleanup_systemd_service() {
local CONTAINER_NAME="$1"
echo "Cleaning up systemd service for container: $CONTAINER_NAME..."
systemctl --user stop "container-$CONTAINER_NAME.service"
systemctl --user disable "container-$CONTAINER_NAME.service"
systemctl --user daemon-reload >/dev/null
rm -f "$SYSTEMD_USER_DIR/container-$CONTAINER_NAME.service"
}
# Function to create systemd service
create_systemd_service() {
local CONTAINER_NAME="$1"
local RESTART_POLICY="$2"
local MAX_RETRIES="$3"
echo "Creating systemd service for container: $CONTAINER_NAME with restart policy: $RESTART_POLICY..."
if ! podman generate systemd --new --name "$CONTAINER_NAME" -f >/dev/null; then
echo "Error: Failed to generate systemd service file for '$CONTAINER_NAME'."
exit 1
fi
mkdir -p "$SYSTEMD_USER_DIR"
mv -v "container-$CONTAINER_NAME.service" "$SYSTEMD_USER_DIR/" >/dev/null
# Update systemd service file for restart policy
sed -i "s/^Restart=.*/Restart=$RESTART_POLICY/" "$SYSTEMD_USER_DIR/container-$CONTAINER_NAME.service"
if [ "$RESTART_POLICY" = "on-failure" ] && [ -n "$MAX_RETRIES" ]; then
sed -i "/^\[Service\]$/a RestartSec=5\nStartLimitBurst=$MAX_RETRIES" "$SYSTEMD_USER_DIR/container-$CONTAINER_NAME.service"
fi
systemctl --user daemon-reload >/dev/null
systemctl --user enable "container-$CONTAINER_NAME.service" >/dev/null
if ! systemctl --user start "container-$CONTAINER_NAME.service" >/dev/null; then
echo "Error: Failed to start systemd service for '$CONTAINER_NAME'."
exit 1
fi
systemctl --user status "container-$CONTAINER_NAME.service" >/dev/null
echo "Success systemd container-$CONTAINER_NAME.service created!"
}
# Function to handle container removal
handle_container_removal() {
local CONTAINER_NAME="$1"
if systemctl --user is-active "container-$CONTAINER_NAME.service" >/dev/null 2>&1 || [ -f ~/.config/systemd/user/container-$CONTAINER_NAME.service ]; then
cleanup_systemd_service "$CONTAINER_NAME"
elif podman inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
CONTAINER_NAME=$(podman inspect --format '{{.Name}}' "$CONTAINER_NAME" | sed 's/\///')
cleanup_systemd_service "$CONTAINER_NAME"
else
echo "Error: Container '$CONTAINER_NAME' does not exist."
fi
}
# Function to handle image removal
handle_image_removal() {
local IMAGE_ID="$1"
local CONTAINERS
CONTAINERS=$(podman ps -a --filter "ancestor=$IMAGE_ID" --format "{{.Names}}")
for CONTAINER in $CONTAINERS; do
handle_container_removal "$CONTAINER"
done
}
# Extract the container name from the arguments
extract_container_name() {
local args="$1"
echo "$args" | awk '{for (i=1;i<=NF;i++) if ($i == "--name") print $(i+1)}'
}
# Extract the restart policy from the arguments
extract_restart_policy() {
local args="$1"
echo "$args" | awk -F '--restart=' '{if (NF>1) print $2}' | awk '{print $1}'
}
# Extract max retries for on-failure policy
extract_max_retries() {
local restart_policy="$1"
if [[ "$restart_policy" == on-failure:* ]]; then
echo "$restart_policy" | awk -F ':' '{print $2}'
fi
}
# Get the systemd restart policy for a container
get_systemd_restart_policy() {
local CONTAINER_NAME="$1"
if [ -f "$SYSTEMD_USER_DIR/container-$CONTAINER_NAME.service" ]; then
grep "^Restart=" "$SYSTEMD_USER_DIR/container-$CONTAINER_NAME.service" | cut -d'=' -f2
else
echo "no"
fi
}
# Main script logic
podman_command="$*"
CONTAINER_NAME=$(extract_container_name "$podman_command")
RESTART_POLICY=$(extract_restart_policy "$podman_command")
MAX_RETRIES=$(extract_max_retries "$RESTART_POLICY")
# Normalize restart policy for systemd
if [[ "$RESTART_POLICY" == "on-failure"* ]]; then
RESTART_POLICY="on-failure"
elif [[ "$RESTART_POLICY" == "always" ]]; then
RESTART_POLICY="always"
elif [[ "$RESTART_POLICY" == "unless-stopped" ]]; then
RESTART_POLICY="unless-stopped"
else
RESTART_POLICY="no"
fi
# If the podman command is to stop a container, handle the systemd service
if [ "$1" = "stop" ]; then
for arg in "$@"; do
if [[ "$arg" != "stop" ]]; then
CONTAINER_NAME=$(podman inspect --format '{{.Name}}' "$arg" | sed 's/\///')
SYSTEMD_RESTART_POLICY=$(get_systemd_restart_policy "$CONTAINER_NAME")
if [[ "$SYSTEMD_RESTART_POLICY" == "always" ]]; then
echo "Stopping container and leaving systemd service active for: $CONTAINER_NAME"
podman stop "$arg"
elif [[ "$SYSTEMD_RESTART_POLICY" == "unless-stopped" ]]; then
echo "Stopping container and systemd service for: $CONTAINER_NAME"
systemctl --user stop --now "container-$CONTAINER_NAME.service"
systemctl --user disable "container-$CONTAINER_NAME.service"
systemctl --user daemon-reload >/dev/null
podman stop "$arg"
else
podman stop "$arg"
fi
fi
done
exit 0
fi
# If the podman command is to start a container, handle the systemd service
if [ "$1" = "start" ]; then
for arg in "$@"; do
if [[ "$arg" != "start" ]]; then
CONTAINER_NAME=$(podman inspect --format '{{.Name}}' "$arg" | sed 's/\///')
SYSTEMD_RESTART_POLICY=$(get_systemd_restart_policy "$CONTAINER_NAME")
if [[ "$SYSTEMD_RESTART_POLICY" == "always" ]]; then
echo "Starting systemd service for container: $CONTAINER_NAME"
systemctl --user start "container-$CONTAINER_NAME.service"
elif [[ "$SYSTEMD_RESTART_POLICY" == "unless-stopped" ]]; then
echo "Starting systemd service for container: $CONTAINER_NAME"
create_systemd_service "$CONTAINER_NAME" "$RESTART_POLICY" "$MAX_RETRIES"
else
podman start "$arg"
fi
fi
done
exit 0
fi
# If the podman command is to remove a container, clean up the systemd service
if [ "$1" = "container" ] && [ "$2" = "rm" ]; then
for arg in "$@"; do
if [[ "$arg" != "rm" && "$arg" != "-f" ]]; then
handle_container_removal "$arg"
fi
done
elif [ "$1" = "rm" ]; then
for arg in "$@"; do
if [[ "$arg" != "rm" && "$arg" != "-f" ]]; then
handle_container_removal "$arg"
fi
done
elif [ "$1" = "rmi" ]; then
for arg in "$@"; do
if [[ "$arg" != "rmi" && "$arg" != "--force" ]]; then
handle_image_removal "$arg"
fi
done
fi
# Run the original Podman command
/usr/bin/podman "$@"
# Apply systemd service if restart policy is always or unless-stopped
if [[ "$RESTART_POLICY" == "always" || "$RESTART_POLICY" == "unless-stopped" ]]; then
if [ -z "$CONTAINER_NAME" ]; then
echo "Error: Container name is required when using --restart."
exit 1
fi
# Check if the systemd service is active and the systemd service file exists
if systemctl --user is-active "container-$CONTAINER_NAME.service" >/dev/null 2>&1; then
echo "Existing systemd service found for container '$CONTAINER_NAME'. Disabling and removing it."
cleanup_systemd_service "$CONTAINER_NAME"
fi
create_systemd_service "$CONTAINER_NAME" "$RESTART_POLICY" "$MAX_RETRIES"
fi
-
Make the Script Executable
chmod +x ~/manage_podman.sh
Step 2: Set Up Bash Aliases
-
Open or Create
~/.bash_aliasesnano ~/.bash_aliases -
Add the Alias
Add the following line to create an alias for the script:
alias podman='~/manage_podman.sh' -
Ensure
~/.bash_aliasesis LoadedOpen
~/.bashrc:nano ~/.bashrcRemove the
#from the following lines (if present) to ensure~/.bash_aliasesis loaded:# if [ -f ~/.bash_aliases ]; then
# . ~/.bash_aliases
# fiChange to:
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi -
Reload
~/.bashrcApply the changes by reloading
~/.bashrc:source ~/.bashrc
Step 3: Using the Script
Now you can use the alias podman to run your script. For example:
podman run -dit --name test-container --restart=always alpine sh -c "while true; do echo 'Podman is running' > /testfile; sleep 5; done"
This command will use the script to manage the container with systemd service creation and cleanup as needed.
Detailed Explanation
Function cleanup_systemd_service
This function stops, disables, and removes the systemd service for the specified container.
Function create_systemd_service
This function generates a systemd service file for the specified container, moves it to the appropriate directory, and enables and starts the service.
Function handle_container_removal
This function checks if the container exists and cleans up the associated systemd service if it does.
Function handle_image_removal
This function finds all containers associated with a specified image and cleans up their systemd services before removing the containers.
Container Name Extraction
The script extracts the container name from the command-line arguments to correctly identify the container for systemd service management.
Original Podman Command Execution
The script runs the original Podman command with all the provided arguments, ensuring that all parameters are preserved.
Cleanup on Container and Image Removal
The script detects when a container removal or image removal command is issued and cleans up the associated systemd services.
Conclusion
This script automates the management of Podman containers with systemd, ensuring that containers are restarted according to specified policies and that systemd services are cleaned up when containers or their associated images are removed. By creating an alias, you can use the script seamlessly as a replacement for the podman command, integrating container management into your workflow effortlessly.