Skip to main content

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:

  1. Checks for restart policy flags in the Podman command.
  2. Extracts the container name from the command-line arguments.
  3. Runs the original Podman command with the provided arguments.
  4. Creates or updates a systemd service based on the restart policy.
  5. Cleans up the systemd service when containers are removed.
  6. Handles forcibly removing containers and associated systemd services.
  7. Handles removing images and cleans up services for containers using the image.

The Script

Step 1: Create the Script

  1. Create the Script File

    Create a new file for the script, for example, manage_podman.sh.

    nano ~/manage_podman.sh
  2. Add the Script Content

    Copy and paste the following script into manage_podman.sh:

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

  1. Make the Script Executable

    chmod +x ~/manage_podman.sh

Step 2: Set Up Bash Aliases

  1. Open or Create ~/.bash_aliases

    nano ~/.bash_aliases
  2. Add the Alias

    Add the following line to create an alias for the script:

    alias podman='~/manage_podman.sh'
  3. Ensure ~/.bash_aliases is Loaded

    Open ~/.bashrc:

    nano ~/.bashrc

    Remove the # from the following lines (if present) to ensure ~/.bash_aliases is loaded:

    # if [ -f ~/.bash_aliases ]; then
    # . ~/.bash_aliases
    # fi

    Change to:

    if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
    fi
  4. Reload ~/.bashrc

    Apply 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.