Compose remote images with cURL and ImageMagick
Combining a downloaded background with an overlay is useful for reports, dashboards, and generated previews. This example composes NASA imagery; it is not a live weather-data integration. The same processing structure can work with a weather provider once its image URLs, licensing, and refresh requirements are verified.
Version compatibility
Use Bash, ImageMagick 7 with the magick command, and cURL 8.4 or newer. The cURL requirement matters
because the download-size limit must also stop responses without a known content length. Keep both
download and image-decoding tools on maintained releases.
Overview
A reliable pipeline must check the downloads it actually processes, not fetch once to test a URL and then fetch again unchecked. The script downloads each source into a private job directory, composes the image, and removes temporary inputs. A new output directory prevents collisions with previous results.
Setting up the pipeline
The example uses a moon-landing photo and a NASA logo from NASA-hosted URLs. Observe NASA's image and media usage guidance, including restrictions on use of its insignia; these sample assets do not imply endorsement.
Process substitution requirements
Process substitution such as <(curl ...) is a Bash feature, not a POSIX-shell requirement. More
importantly, a background download failure does not automatically become the image command's exit
status. This implementation uses explicitly checked downloads instead of process substitution.
Fetching and compositing images on-the-fly
Save the following as compose.sh. Supply two trusted HTTPS URLs and a new output directory:
#!/bin/bash
set -euo pipefail
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <background-https-url> <overlay-https-url> <new-output-directory>" >&2
exit 1
fi
background_url=$1
overlay_url=$2
output_dir=$3
[[ $output_dir = /* ]] || output_dir=$PWD/$output_dir
if ! mkdir -m 700 -- "$output_dir"; then
echo "Use a new output directory" >&2
exit 1
fi
cleanup() {
local status=$?
rm -f -- "$output_dir/background.image" "$output_dir/overlay.image"
if [ "$status" -ne 0 ]; then
rm -f -- "$output_dir/result.jpg"
rmdir -- "$output_dir"
fi
}
trap cleanup EXIT
fetch_image() {
curl --disable --globoff --fail --silent --show-error --location \
--proto '=https' --proto-redir '=https' --max-redirs 3 \
--connect-timeout 10 --max-time 30 --retry 2 --retry-max-time 60 \
--max-filesize 10485760 --output "$2" --url "$1"
}
fetch_image "$background_url" "$output_dir/background.image"
fetch_image "$overlay_url" "$output_dir/overlay.image"
cd -P -- "$output_dir"
output_dir=$PWD
magick -limit memory 256MiB -limit map 512MiB -limit disk 1GiB \
-limit width 10000 -limit height 10000 \
background.image -resize '1600x1600>' \
\( overlay.image -resize '300x300>' \) \
-gravity southeast -geometry +20+20 -composite result.jpg
[ -s result.jpg ] || { echo "No image produced" >&2; exit 1; }
echo "Composite saved: $output_dir/result.jpg"
Run it with fresh output storage:
bash compose.sh \
'https://images-assets.nasa.gov/image/as11-40-5874/as11-40-5874~orig.jpg' \
'https://www.nasa.gov/wp-content/uploads/2023/04/nasa-logo-web-rgb.png' \
nasa-composite
A completed job contains result.jpg. Download or decoding failures return a nonzero exit status
and remove only the job's own partial files. Do not let other processes write into that directory
while the job runs.
Memory considerations
A small compressed file can decode into a large image. The script limits downloaded bytes, decoded image dimensions, and ImageMagick cache resources, but those settings are not a complete process memory or execution-time limit. ImageMagick may use temporary disk storage for pixel caches. Configure its security policy and enforce operating-system memory, disk, and time limits for production workers.
Security considerations
Use known, trusted image sources. HTTPS-only transfer and certificate verification do not prevent SSRF: an arbitrary URL or redirect can still point at a private network service. A public upload or URL-processing API needs a separate origin policy, redirect validation, network egress controls, and authentication. Do not expose this shell script as an unrestricted URL fetcher.
Do not disable TLS verification or certificate-revocation checks to make a failing download pass.
Keep cURL configuration and credentials out of this worker; --disable prevents automatic loading
of the user's default curl configuration file.
Real-world applications
This structure can compose dashboard imagery, event overlays, or report illustrations. Validate the actual upstream data source, refresh policy, image dimensions, and usage rights for each workflow. The NASA example demonstrates compositing, not weather forecasting or a latency benchmark.
Error handling and optimization
The one fetch_image helper owns download options and retry policy for both sources. Retries are
bounded and apply to cURL's retryable failures. A 404 or invalid image must fail visibly rather than
being treated as an empty but successful overlay. Check the script's exit status before publishing
its result.
Caching validated inputs can reduce repeated downloads. Keep cache freshness and integrity explicit; do not add a second unverified fetch after a successful check.
Conclusion
cURL and ImageMagick form a useful automation pipeline when the actual transfers, decoder result, and output ownership are checked. Reuse one download policy, keep intermediate files private, and inspect the composite before publishing it.
For managed image workflows, explore Transloadit's image processing service.
