Skip to content

Commit f24e74c

Browse files
djmbnpezza93
authored andcommitted
Use local docker registry to push and pull app images
Allow applications to be deployed without needing to set up a repository in a remote Docker registry. If the registry server starts with `localhost`, Kamal will start a local docker registry on that port and push the app image to it. Then when pulling the image onto the servers, we use net-ssh to forward the that port from the app server to the deployment server.
1 parent 1547089 commit f24e74c

File tree

19 files changed

+323
-91
lines changed

19 files changed

+323
-91
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Setup Ruby and install gems
1818
uses: ruby/setup-ruby@v1
1919
with:
20-
ruby-version: 3.3.0
20+
ruby-version: 3.4.1
2121
bundler-cache: true
2222
- name: Run Rubocop
2323
run: bundle exec rubocop --parallel
@@ -28,7 +28,7 @@ jobs:
2828
- "3.1"
2929
- "3.2"
3030
- "3.3"
31-
- "3.4.0-preview2"
31+
- "3.4"
3232
gemfile:
3333
- Gemfile
3434
- gemfiles/rails_edge.gemfile

Gemfile.lock

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,23 @@ PATH
1616
GEM
1717
remote: https://rubygems.org/
1818
specs:
19-
actionpack (8.0.0.1)
20-
actionview (= 8.0.0.1)
21-
activesupport (= 8.0.0.1)
19+
actionpack (8.0.1)
20+
actionview (= 8.0.1)
21+
activesupport (= 8.0.1)
2222
nokogiri (>= 1.8.5)
2323
rack (>= 2.2.4)
2424
rack-session (>= 1.0.1)
2525
rack-test (>= 0.6.3)
2626
rails-dom-testing (~> 2.2)
2727
rails-html-sanitizer (~> 1.6)
2828
useragent (~> 0.16)
29-
actionview (8.0.0.1)
30-
activesupport (= 8.0.0.1)
29+
actionview (8.0.1)
30+
activesupport (= 8.0.1)
3131
builder (~> 3.1)
3232
erubi (~> 1.11)
3333
rails-dom-testing (~> 2.2)
3434
rails-html-sanitizer (~> 1.6)
35-
activesupport (8.0.0.1)
35+
activesupport (8.0.1)
3636
base64
3737
benchmark (>= 0.3)
3838
bigdecimal
@@ -48,32 +48,30 @@ GEM
4848
ast (2.4.2)
4949
base64 (0.2.0)
5050
bcrypt_pbkdf (1.1.1)
51-
bcrypt_pbkdf (1.1.1-arm64-darwin)
52-
bcrypt_pbkdf (1.1.1-x86_64-darwin)
5351
benchmark (0.4.0)
54-
bigdecimal (3.1.8)
52+
bigdecimal (3.1.9)
5553
builder (3.3.0)
5654
concurrent-ruby (1.3.4)
57-
connection_pool (2.4.1)
55+
connection_pool (2.5.0)
5856
crass (1.0.6)
5957
date (3.4.1)
60-
debug (1.9.2)
58+
debug (1.10.0)
6159
irb (~> 1.10)
6260
reline (>= 0.3.8)
63-
dotenv (3.1.5)
61+
dotenv (3.1.7)
6462
drb (2.2.1)
6563
ed25519 (1.3.0)
66-
erubi (1.13.0)
64+
erubi (1.13.1)
6765
i18n (1.14.6)
6866
concurrent-ruby (~> 1.0)
6967
io-console (0.8.0)
70-
irb (1.14.2)
68+
irb (1.14.3)
7169
rdoc (>= 4.0.0)
7270
reline (>= 0.4.2)
73-
json (2.9.0)
71+
json (2.9.1)
7472
language_server-protocol (3.17.0.3)
75-
logger (1.6.3)
76-
loofah (2.23.1)
73+
logger (1.6.5)
74+
loofah (2.24.0)
7775
crass (~> 1.0.2)
7876
nokogiri (>= 1.12.0)
7977
minitest (5.25.4)
@@ -84,25 +82,26 @@ GEM
8482
net-sftp (4.0.0)
8583
net-ssh (>= 5.0.0, < 8.0.0)
8684
net-ssh (7.3.0)
87-
nokogiri (1.17.2-arm64-darwin)
85+
nokogiri (1.18.1-arm64-darwin)
8886
racc (~> 1.4)
89-
nokogiri (1.17.2-x86_64-darwin)
87+
nokogiri (1.18.1-x86_64-darwin)
9088
racc (~> 1.4)
91-
nokogiri (1.17.2-x86_64-linux)
89+
nokogiri (1.18.1-x86_64-linux-gnu)
9290
racc (~> 1.4)
9391
ostruct (0.6.1)
9492
parallel (1.26.3)
9593
parser (3.3.6.0)
9694
ast (~> 2.4.1)
9795
racc
98-
psych (5.2.1)
96+
psych (5.2.2)
9997
date
10098
stringio
10199
racc (1.8.1)
102100
rack (3.1.8)
103-
rack-session (2.0.0)
101+
rack-session (2.1.0)
102+
base64 (>= 0.1.0)
104103
rack (>= 3.0.0)
105-
rack-test (2.1.0)
104+
rack-test (2.2.0)
106105
rack (>= 1.3)
107106
rackup (2.2.1)
108107
rack (>= 3)
@@ -113,22 +112,22 @@ GEM
113112
rails-html-sanitizer (1.6.2)
114113
loofah (~> 2.21)
115114
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
116-
railties (8.0.0.1)
117-
actionpack (= 8.0.0.1)
118-
activesupport (= 8.0.0.1)
115+
railties (8.0.1)
116+
actionpack (= 8.0.1)
117+
activesupport (= 8.0.1)
119118
irb (~> 1.13)
120119
rackup (>= 1.0.0)
121120
rake (>= 12.2)
122121
thor (~> 1.0, >= 1.2.2)
123122
zeitwerk (~> 2.6)
124123
rainbow (3.1.1)
125124
rake (13.2.1)
126-
rdoc (6.8.1)
125+
rdoc (6.10.0)
127126
psych (>= 4.0.0)
128-
regexp_parser (2.9.3)
129-
reline (0.5.12)
127+
regexp_parser (2.10.0)
128+
reline (0.6.0)
130129
io-console (~> 0.5)
131-
rubocop (1.69.2)
130+
rubocop (1.70.0)
132131
json (~> 2.3)
133132
language_server-protocol (>= 3.17.0)
134133
parallel (~> 1.10)
@@ -138,15 +137,15 @@ GEM
138137
rubocop-ast (>= 1.36.2, < 2.0)
139138
ruby-progressbar (~> 1.7)
140139
unicode-display_width (>= 2.4.0, < 4.0)
141-
rubocop-ast (1.36.2)
140+
rubocop-ast (1.37.0)
142141
parser (>= 3.3.1.0)
143142
rubocop-minitest (0.36.0)
144143
rubocop (>= 1.61, < 2.0)
145144
rubocop-ast (>= 1.31.1, < 2.0)
146-
rubocop-performance (1.23.0)
145+
rubocop-performance (1.23.1)
147146
rubocop (>= 1.48.1, < 2.0)
148147
rubocop-ast (>= 1.31.1, < 2.0)
149-
rubocop-rails (2.27.0)
148+
rubocop-rails (2.28.0)
150149
activesupport (>= 4.2.0)
151150
rack (>= 1.1)
152151
rubocop (>= 1.52.0, < 2.0)
@@ -158,7 +157,7 @@ GEM
158157
rubocop-rails
159158
ruby-progressbar (1.13.0)
160159
ruby2_keywords (0.0.5)
161-
securerandom (0.4.0)
160+
securerandom (0.4.1)
162161
sshkit (1.23.2)
163162
base64
164163
net-scp (>= 1.1.2)
@@ -169,7 +168,7 @@ GEM
169168
thor (1.3.2)
170169
tzinfo (2.0.6)
171170
concurrent-ruby (~> 1.0)
172-
unicode-display_width (3.1.2)
171+
unicode-display_width (3.1.4)
173172
unicode-emoji (~> 4.0, >= 4.0.4)
174173
unicode-emoji (4.0.4)
175174
uri (1.0.2)
@@ -189,4 +188,4 @@ DEPENDENCIES
189188
rubocop-rails-omakase
190189

191190
BUNDLED WITH
192-
2.4.3
191+
2.6.2

lib/kamal/cli/build.rb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,16 @@ def push
6060

6161
desc "pull", "Pull app image from registry onto servers"
6262
def pull
63-
if (first_hosts = mirror_hosts).any?
64-
#  Pull on a single host per mirror first to seed them
65-
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
66-
pull_on_hosts(first_hosts)
67-
say "Pulling image on remaining hosts...", :magenta
68-
pull_on_hosts(KAMAL.hosts - first_hosts)
69-
else
70-
pull_on_hosts(KAMAL.hosts)
63+
Kamal::Cli::PortForwarding.new(KAMAL.hosts, KAMAL.config.registry.local_port).forward do
64+
if (first_hosts = mirror_hosts).any?
65+
#  Pull on a single host per mirror first to seed them
66+
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
67+
pull_on_hosts(first_hosts)
68+
say "Pulling image on remaining hosts...", :magenta
69+
pull_on_hosts(KAMAL.hosts - first_hosts)
70+
else
71+
pull_on_hosts(KAMAL.hosts)
72+
end
7173
end
7274
end
7375

lib/kamal/cli/main.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def deploy
2222
invoke_options = deploy_options
2323

2424
say "Log into image registry...", :magenta
25-
invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
25+
invoke "kamal:cli:registry:setup", [], invoke_options.merge(skip_local: options[:skip_push])
2626

2727
if options[:skip_push]
2828
say "Pull app image...", :magenta
@@ -184,7 +184,7 @@ def remove
184184
invoke "kamal:cli:app:remove", [], options.without(:confirmed)
185185
invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
186186
invoke "kamal:cli:accessory:remove", [ "all" ], options
187-
invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
187+
invoke "kamal:cli:registry:remove", [], options.without(:confirmed).merge(skip_local: true)
188188
end
189189
end
190190
end

lib/kamal/cli/port_forwarding.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
class Kamal::Cli::PortForwarding
2+
attr_reader :hosts, :port
3+
4+
def initialize(hosts, port)
5+
@hosts = hosts
6+
@port = port
7+
end
8+
9+
def forward
10+
if KAMAL.config.registry.local?
11+
@done = false
12+
forward_ports
13+
end
14+
15+
yield
16+
ensure
17+
stop
18+
end
19+
20+
private
21+
22+
def stop
23+
@done = true
24+
@threads.to_a.each(&:join)
25+
end
26+
27+
def forward_ports
28+
@threads = hosts.map do |host|
29+
Thread.new do
30+
Net::SSH.start(host, KAMAL.config.ssh.user) do |ssh|
31+
ssh.forward.remote(port, "localhost", port, "localhost")
32+
ssh.loop(0.1) do
33+
if @done
34+
ssh.forward.cancel_remote(port, "localhost")
35+
break
36+
else
37+
true
38+
end
39+
end
40+
end
41+
end
42+
end
43+
end
44+
end

lib/kamal/cli/registry.rb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
class Kamal::Cli::Registry < Kamal::Cli::Base
2-
desc "login", "Log in to registry locally and remotely"
2+
desc "setup", "Setup local registry or log in to remote registry locally and remotely"
33
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
44
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
5-
def login
6-
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
7-
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
5+
def setup
6+
if KAMAL.registry.local?
7+
run_locally { execute *KAMAL.registry.setup } unless options[:skip_local]
8+
else
9+
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
10+
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
11+
end
812
end
913

10-
desc "logout", "Log out of registry locally and remotely"
14+
desc "remove", "Remove local registry or log out of remote registry locally and remotely"
1115
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
1216
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
13-
def logout
14-
run_locally { execute *KAMAL.registry.logout } unless options[:skip_local]
15-
on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
17+
def remove
18+
if KAMAL.registry.local?
19+
run_locally { execute *KAMAL.registry.remove, raise_on_non_zero_exit: false } unless options[:skip_local]
20+
else
21+
run_locally { execute *KAMAL.registry.logout } unless options[:skip_local]
22+
on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
23+
end
1624
end
1725
end
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
22
def create
3-
docker :buildx, :create, "--name", builder_name, "--driver=#{driver}" unless docker_driver?
3+
return if docker_driver?
4+
5+
options =
6+
if KAMAL.registry.local?
7+
"--driver=#{driver} --driver-opt network=host"
8+
else
9+
"--driver=#{driver}"
10+
end
11+
12+
docker :buildx, :create, "--name", builder_name, options
413
end
514

615
def remove
@@ -9,6 +18,10 @@ def remove
918

1019
private
1120
def builder_name
12-
"kamal-local-#{driver}"
21+
if KAMAL.registry.local?
22+
"kamal-local-registry-#{driver}"
23+
else
24+
"kamal-local-#{driver}"
25+
end
1326
end
1427
end

lib/kamal/commands/registry.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
class Kamal::Commands::Registry < Kamal::Commands::Base
22
delegate :registry, to: :config
3+
delegate :local?, :local_port, to: :registry
34

45
def login
6+
return if local?
7+
58
docker :login,
69
registry.server,
710
"-u", sensitive(Kamal::Utils.escape_shell_value(registry.username)),
@@ -11,4 +14,18 @@ def login
1114
def logout
1215
docker :logout, registry.server
1316
end
17+
18+
def setup
19+
combine \
20+
docker(:start, "kamal-docker-registry"),
21+
docker(:run, "--detach", "-p", "127.0.0.1:#{local_port}:5000", "--name", "kamal-docker-registry", "registry:2"),
22+
by: "||"
23+
end
24+
25+
def remove
26+
combine \
27+
docker(:stop, "kamal-docker-registry"),
28+
docker(:rm, "kamal-docker-registry"),
29+
by: "&&"
30+
end
1431
end

lib/kamal/configuration/registry.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ def password
2121
lookup("password")
2222
end
2323

24+
def local?
25+
server&.match?("^localhost[:$]")
26+
end
27+
28+
def local_port
29+
local? ? (server.split(":").last.to_i || 80) : nil
30+
end
31+
2432
private
2533
def lookup(key)
2634
if registry_config[key].is_a?(Array)

0 commit comments

Comments
 (0)