2
2
set -euo pipefail
3
3
4
4
# Return metadata expected by the Docker CLI plugin framework: https://github.com/docker/cli/pull/1564
5
- # TODO: update ShortDescription and Version as needed.
6
5
if [ " ${1:- } " = " docker-cli-plugin-metadata" ]; then
7
6
cat << EOF
8
7
{
9
8
"SchemaVersion": "0.1.0",
10
9
"Vendor": "https://github.com/psviderski",
11
10
"Version": "0.1.0",
12
- "ShortDescription": "Push Docker images to remote daemons via SSH using unregistry "
11
+ "ShortDescription": "Upload image to remote Docker daemon via SSH without external registry "
13
12
}
14
13
EOF
15
14
exit 0
16
15
fi
17
16
18
17
UNREGISTRY_IMAGE=" ghcr.io/psviderski/unregistry:latest"
19
18
20
- # Colors for output.
19
+ # Colors and symbols for output.
21
20
RED=' \033[0;31m'
22
21
GREEN=' \033[0;32m'
23
22
YELLOW=' \033[0;33m'
23
+ BLUE=' \033[0;34m'
24
24
NC=' \033[0m' # no color
25
25
26
26
info () {
27
- echo -e " ${GREEN} [INFO]${NC} $1 "
27
+ echo -e " ${BLUE} •${NC} $1 "
28
+ }
29
+
30
+ success () {
31
+ echo -e " ${GREEN} ✓${NC} $1 "
28
32
}
29
33
30
34
warning () {
31
- echo -e " ${YELLOW} [WARNING] ${NC} $1 "
35
+ echo -e " ${YELLOW} ! ${NC} $1 "
32
36
}
33
37
34
38
error () {
35
- echo -e " ${RED} [ ERROR] ${NC} $1 " >&2
39
+ echo -e " ${RED} ERROR: ${NC} $1 " >&2
36
40
exit 1
37
41
}
38
42
43
+ usage () {
44
+ echo " Usage: docker pussh [OPTIONS] IMAGE[:TAG] [USER@]HOST[:PORT]"
45
+ echo " "
46
+ echo " Upload a Docker image to a remote Docker daemon via SSH without an external registry."
47
+ echo " "
48
+ echo " Options:"
49
+ echo " -h, --help Show this help message."
50
+ echo " -i, --ssh-key path Path to SSH private key for remote login (if not already added to SSH agent)."
51
+ echo " --platform string Push a specific platform for a multi-platform image (e.g., linux/amd64, linux/arm64)."
52
+ echo " Local Docker has to use containerd image store to support multi-platform images."
53
+ echo " "
54
+ echo " Examples:"
55
+ echo " docker pussh myimage:latest user@host"
56
+ echo " docker pussh --platform linux/amd64 myimage:latest host"
57
+ echo " docker pussh myimage:latest user@host:2222 -i ~/.ssh/id_ed25519"
58
+ }
59
+
39
60
# SSH command arguments to be used for all ssh commands after establishing a shared "master" connection
40
61
# using ssh_remote.
41
62
declare -a SSH_ARGS=()
@@ -59,7 +80,7 @@ ssh_remote() {
59
80
60
81
# Establish ControlMaster connection in the background.
61
82
if ! ssh " ${ssh_opts[@]} " -f -N " $ssh_addr " ; then
62
- error " failed to connect to remote host via SSH: $ssh_addr "
83
+ error " Failed to connect to remote host via SSH: $ssh_addr "
63
84
fi
64
85
65
86
# Populate SSH_ARGS array for reuse in all subsequent commands.
@@ -84,7 +105,7 @@ check_remote_docker() {
84
105
if ssh " ${SSH_ARGS[@]} " " [ \$ (id -u) -ne 0 ] && sudo docker version" > /dev/null; then
85
106
REMOTE_SUDO=" sudo"
86
107
else
87
- error " failed to run docker commands on remote host. Please ensure:
108
+ error " Failed to run docker commands on remote host. Please ensure:
88
109
- Docker is installed and running on the remote host
89
110
- SSH user has permissions to run docker commands (user is root or non-root user is in 'docker' group)
90
111
- If sudo is required, ensure the user can run 'sudo docker' without a password prompt"
@@ -130,11 +151,11 @@ run_unregistry() {
130
151
ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker rm -f $UNREGISTRY_CONTAINER " > /dev/null 2>&1 || true
131
152
# Check if the error is due to port binding.
132
153
if ! echo " $output " | grep -q --ignore-case " bind.*$UNREGISTRY_PORT " ; then
133
- error " failed to start unregistry container:\n$output "
154
+ error " Failed to start unregistry container:\n$output "
134
155
fi
135
156
done
136
157
137
- error " failed to start unregistry container:\n$output "
158
+ error " Failed to start unregistry container:\n$output "
138
159
}
139
160
140
161
# Forward a local port to a remote port over the established SSH connection.
@@ -158,10 +179,10 @@ forward_port() {
158
179
return 0
159
180
fi
160
181
161
- error " failed to forward local port $local_port to remote unregistry port 127.0.0.1:$remote_port : $output "
182
+ error " Failed to forward local port $local_port to remote unregistry port 127.0.0.1:$remote_port : $output "
162
183
done
163
184
164
- error " failed to find an available local port to forward to remote unregistry port. Please try again."
185
+ error " Failed to find an available local port to forward to remote unregistry port. Please try again."
165
186
}
166
187
167
188
# Check if the local Docker server is Docker Desktop.
@@ -201,28 +222,11 @@ run_docker_desktop_tunnel() {
201
222
docker rm -f " $DOCKER_DESKTOP_TUNNEL_CONTAINER " > /dev/null 2>&1 || true
202
223
# Check if error is due to port binding.
203
224
if ! echo " $output " | grep -q --ignore-case " bind.*$DOCKER_DESKTOP_TUNNEL_PORT " ; then
204
- error " failed to create a tunnel from Docker Desktop VM to localhost:$host_port :\n$output "
225
+ error " Failed to create a tunnel from Docker Desktop VM to localhost:$host_port :\n$output "
205
226
fi
206
227
done
207
228
208
- error " failed to create a tunnel from Docker Desktop VM to localhost:$host_port :\n$output "
209
- }
210
-
211
- usage () {
212
- echo " Usage: docker pussh [OPTIONS] IMAGE[:TAG] [USER@]HOST[:PORT]"
213
- echo " "
214
- echo " Upload a Docker image to a remote Docker daemon via SSH."
215
- echo " "
216
- echo " Options:"
217
- echo " -h, --help Show this help message."
218
- echo " -i, --ssh-key path Path to SSH private key for remote login (if not already added to SSH agent)."
219
- echo " --platform string Push a specific platform for a multi-platform image (e.g., linux/amd64, linux/arm64)."
220
- echo " Local Docker has to use containerd image store to support multi-platform images."
221
- echo " "
222
- echo " Examples:"
223
- echo " docker pussh myimage:latest user@host"
224
- echo " docker pussh --platform linux/amd64 myimage:latest host"
225
- echo " docker pussh myimage:latest user@host:2222 -i ~/.ssh/id_ed25519"
229
+ error " Failed to create a tunnel from Docker Desktop VM to localhost:$host_port :\n$output "
226
230
}
227
231
228
232
DOCKER_PLATFORM=" "
@@ -258,7 +262,7 @@ while [ $# -gt 0 ]; do
258
262
exit 0
259
263
;;
260
264
-* )
261
- error " unknown option: $1 \n$help_command "
265
+ error " Unknown option: $1 \n$help_command "
262
266
;;
263
267
* )
264
268
# First non-option argument is the image.
@@ -268,7 +272,7 @@ while [ $# -gt 0 ]; do
268
272
elif [ -z " $SSH_ADDRESS " ]; then
269
273
SSH_ADDRESS=" $1 "
270
274
else
271
- error " too many arguments.\n$help_command "
275
+ error " Too many arguments.\n$help_command "
272
276
fi
273
277
shift
274
278
;;
@@ -322,19 +326,19 @@ check_remote_docker
322
326
323
327
info " Starting unregistry container on remote host..."
324
328
run_unregistry
325
- info " Unregistry is listening localhost:$UNREGISTRY_PORT on remote host."
329
+ success " Unregistry is listening localhost:$UNREGISTRY_PORT on remote host."
326
330
327
331
# Forward random local port to remote unregistry port through established SSH connection.
328
332
LOCAL_PORT=$( forward_port " $UNREGISTRY_PORT " )
329
- info " Forwarded localhost:$LOCAL_PORT to unregistry over SSH connection."
333
+ success " Forwarded localhost:$LOCAL_PORT to unregistry over SSH connection."
330
334
331
335
# Handle Docker Desktop on macOS.
332
336
PUSH_PORT=$LOCAL_PORT
333
337
if is_docker_desktop; then
334
338
info " Detected Docker Desktop, creating additional tunnel from Docker Desktop VM to localhost:$LOCAL_PORT ..."
335
339
run_docker_desktop_tunnel " $LOCAL_PORT "
336
340
PUSH_PORT=$DOCKER_DESKTOP_TUNNEL_PORT
337
- info " Docker Desktop tunnel created: localhost:$PUSH_PORT → localhost:$LOCAL_PORT "
341
+ success " Docker Desktop tunnel created: localhost:$PUSH_PORT → localhost:$LOCAL_PORT "
338
342
fi
339
343
340
344
# Tag and push the image to unregistry through the forwarded port.
@@ -356,15 +360,15 @@ if ! ssh "${SSH_ARGS[@]}" "$REMOTE_SUDO docker info -f '{{ .DriverStatus }}' | g
356
360
357
361
remote_registry_image=" localhost:$UNREGISTRY_PORT /$IMAGE "
358
362
if ! ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker pull $remote_registry_image " ; then
359
- error " failed to pull image from unregistry on remote host."
363
+ error " Failed to pull image from unregistry on remote host."
360
364
fi
361
365
if ! ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker tag $remote_registry_image $IMAGE " ; then
362
- error " failed to retag image on remote host $remote_registry_image → $IMAGE "
366
+ error " Failed to retag image on remote host $remote_registry_image → $IMAGE "
363
367
fi
364
368
ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker rmi $remote_registry_image " > /dev/null || true
365
369
fi
366
370
367
371
info " Removing unregistry container on remote host..."
368
372
ssh " ${SSH_ARGS[@]} " " $REMOTE_SUDO docker rm -f $UNREGISTRY_CONTAINER " > /dev/null || true
369
373
370
- info " Successfully pushed '$IMAGE ' to $SSH_ADDRESS "
374
+ success " Successfully pushed '$IMAGE ' to $SSH_ADDRESS "
0 commit comments