Last active
March 3, 2026 21:19
-
-
Save chaitanyaSoni96/295a0105c30cf1e15273d6b875c72c49 to your computer and use it in GitHub Desktop.
Deploy podman images to VPS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # ── Arguments ───────────────────────────────────────────────────────────────── | |
| usage() { | |
| echo "Usage: $0 <host> <compose-file> [remote-dir] [--tag <version>]" | |
| exit 1 | |
| } | |
| HOST="" | |
| COMPOSE_FILE="" | |
| REMOTE_DIR="~/app" | |
| TAG="" | |
| # Parse positional args and optional --tag | |
| POSITIONAL=() | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --tag) TAG="$2"; shift 2 ;; | |
| --tag=*) TAG="${1#--tag=}"; shift ;; | |
| -*) echo "Unknown option: $1"; usage ;; | |
| *) POSITIONAL+=("$1"); shift ;; | |
| esac | |
| done | |
| [[ ${#POSITIONAL[@]} -lt 2 ]] && usage | |
| HOST="${POSITIONAL[0]}" | |
| COMPOSE_FILE="${POSITIONAL[1]}" | |
| REMOTE_DIR="${POSITIONAL[2]:-~/app}" | |
| echo "==> Host: $HOST" | |
| echo "==> Compose: $COMPOSE_FILE" | |
| echo "==> Remote dir: $REMOTE_DIR" | |
| echo "==> Tag: ${TAG:-<from compose file>}" | |
| [ -f "$COMPOSE_FILE" ] || { echo "Error: compose file '$COMPOSE_FILE' not found"; exit 1; } | |
| # ── Build images ─────────────────────────────────────────────────────────────── | |
| echo | |
| echo "==> Building images locally" | |
| if [ -n "$TAG" ]; then | |
| APP_VERSION="$TAG" podman compose -f "$COMPOSE_FILE" build | |
| else | |
| podman compose -f "$COMPOSE_FILE" build | |
| fi | |
| # ── Collect images ───────────────────────────────────────────────────────────── | |
| echo | |
| echo "==> Collecting image list" | |
| if [ -n "$TAG" ]; then | |
| IMAGES=$(APP_VERSION="$TAG" podman compose -f "$COMPOSE_FILE" config | awk '/image:/ {print $2}' | sort -u) | |
| else | |
| IMAGES=$(podman compose -f "$COMPOSE_FILE" config | awk '/image:/ {print $2}' | sort -u) | |
| fi | |
| if [ -z "$IMAGES" ]; then | |
| echo "Error: no images found in compose file" | |
| exit 1 | |
| fi | |
| echo "Images:" | |
| echo "$IMAGES" | |
| # ── Transfer images ──────────────────────────────────────────────────────────── | |
| echo | |
| echo "==> Transferring images (layer-aware)" | |
| for IMG in $IMAGES; do | |
| echo " -> $IMG" | |
| podman image scp "$IMG" "$HOST::" | |
| done | |
| # ── Prepare remote directory ─────────────────────────────────────────────────── | |
| echo | |
| echo "==> Preparing remote directory" | |
| ssh "$HOST" "mkdir -p $REMOTE_DIR" | |
| # ── Upload compose file ──────────────────────────────────────────────────────── | |
| echo | |
| echo "==> Uploading compose file" | |
| scp "$COMPOSE_FILE" "$HOST:$REMOTE_DIR/compose.yml" | |
| # ── Enforce pull_policy: never ───────────────────────────────────────────────── | |
| echo | |
| echo "==> Enforcing pull_policy: never" | |
| ssh "$HOST" " | |
| cd $REMOTE_DIR | |
| if ! grep -q pull_policy compose.yml; then | |
| awk ' | |
| /image:/ { print; print \" pull_policy: never\"; next } | |
| { print } | |
| ' compose.yml > compose.tmp && mv compose.tmp compose.yml | |
| fi | |
| " | |
| # ── Start services ───────────────────────────────────────────────────────────── | |
| echo | |
| echo "==> Starting services" | |
| if [ -n "$TAG" ]; then | |
| ssh "$HOST" "cd $REMOTE_DIR && APP_VERSION=$TAG podman compose up -d --remove-orphans" | |
| else | |
| ssh "$HOST" "cd $REMOTE_DIR && podman compose up -d --remove-orphans" | |
| fi | |
| echo | |
| echo "==> Deployment complete!" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
podman-deploy
A bash script for building and deploying Podman Compose services to a remote host — without a registry. Images are transferred directly via
podman image scp.Requirements
podmanandpodman-composeon both local and remote machinesscpavailable locallyUsage
Argument | Required | Description -- | -- | -- host | yes | SSH destination (e.g. user@192.168.1.10) compose-file | yes | Path to your local compose file remote-dir | no | Remote path to deploy to (default: ~/app) --tag | no | Overrides APP_VERSION in the compose fileExamples
What it does
podman compose buildpodman image scp(layer-aware, no registry needed)compose.ymlpull_policy: neverinto the remote compose file so Podman won't try to pull imagespodman compose up -d --remove-orphanson the remote hostCompose file
Services should use
APP_VERSIONfor their image tags. The:-latestfallback means the compose file works fine without--tag.Versioning
Using
--tagthreads the version through the entire deploy:Without
--tag, images are built and run as whatever tag is defined in the compose file (defaulting tolatestwith the example above).