shiplane 0.1.16 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -0
- data/lib/generators/shiplane/install/templates/deploy.rb.erb +40 -12
- data/lib/generators/shiplane/install/templates/production_dockerfile_stages.erb +4 -1
- data/lib/generators/shiplane/install/templates/shiplane.yml.erb +7 -4
- data/lib/shiplane/build.rb +135 -31
- data/lib/shiplane/convert_compose_file.rb +0 -4
- data/lib/shiplane/deploy/container_configuration.rb +3 -1
- data/lib/shiplane/version.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50eae2e296a334c2af2f5ea208d46aabccef50b18d689d60756992086e545a36
|
4
|
+
data.tar.gz: 01763e335f6cae1046cc3808af25b0deeb657de21e52e3721e8b3d46361cf6d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce597515a61d717166c1508e99013b329c40114d9481d0067da045eec1519ee7b39211646cdccd388bae32f2b5c6c1c9dffbf59f29985225e2e7958132758350
|
7
|
+
data.tar.gz: 17be9eabc9af8c6f5425802a994f61cee4aa5084f63875ad5f10d4028669598b8b5d344a16151c0c054456b9e91d063aec506f1d6772e115b85fd2a50bf78bcb
|
data/README.md
CHANGED
@@ -164,6 +164,11 @@ Shiplane::SafeBuild.wrap do
|
|
164
164
|
end
|
165
165
|
```
|
166
166
|
|
167
|
+
#### Using Build Cache
|
168
|
+
Shiplane is designed to always build using the --no-cache flag in order to guaranteee clean, repeatable builds, but if you find yourself troubleshooting or otherwise needing to build multiple times, you might like to use the following flag to speed up the process. Note that this is intended to be a debugging tool. Using it for production building could potentially part of the build process to run in one context and another part to run in another. Such a case might cause both positive and negative results that are difficult to troubleshoot because they are not repeatable. Don't do this, but DO use this responsibly to make your life easier when troubleshooting.
|
169
|
+
|
170
|
+
`USE_BUILD_CACHE=true bundle exec cap production shiplane`
|
171
|
+
|
167
172
|
## Becoming Involved
|
168
173
|
### Community Channels
|
169
174
|
You can join our [Discord community](https://discord.gg/drrn2YG) to ask any questions you might have or to get ahold of someone in the community who might be able to help you (I hang out here just about every day of the week and most of the weekend). There is no guarantee of service implied, but we absolutely believe in helping out our fellow developers and will do so as we are able. If you feel you know some stuff about Shiplane, feel free to hang out and please help out as well!
|
@@ -8,7 +8,7 @@ lock '3.11.0'
|
|
8
8
|
set :application, 'podcaster'
|
9
9
|
set :repo_url, 'git@github.com:kirillian/podcaster.git'
|
10
10
|
|
11
|
-
set :deploy_to, '/var/www/
|
11
|
+
set :deploy_to, '/var/www/my_app_network'
|
12
12
|
|
13
13
|
# Default value for :log_level is :debug
|
14
14
|
# set :log_level, :debug
|
@@ -54,8 +54,21 @@ set :sha, `git rev-parse HEAD`.chomp
|
|
54
54
|
set :shiplane_docker_registry_username, ENV['DOCKERHUB_USERNAME']
|
55
55
|
set :shiplane_docker_registry_password, ENV['DOCKERHUB_PASSWORD']
|
56
56
|
|
57
|
+
|
58
|
+
set :shiplane_build_environment_variables, {
|
59
|
+
# The following example Fetches RAILS_ENV from your Capistrano environment-specific configuration
|
60
|
+
# RAILS_ENV: proc { fetch(:rails_env, 'production') },
|
61
|
+
}
|
62
|
+
|
63
|
+
# The following setting points shiplane to Github's Registry
|
64
|
+
# set :shiplane_docker_registry_url, 'ghcr.io'
|
65
|
+
|
66
|
+
# The following settings assign the username and token/password needed to login to a given registry
|
67
|
+
# set :shiplane_docker_registry_username, ENV['SHIPLANE_CONTAINER_REGISTRY_USERNAME']
|
68
|
+
# set :shiplane_docker_registry_token, ENV['SHIPLANE_CONTAINER_REGISTRY_TOKEN']
|
69
|
+
|
57
70
|
set :shiplane_networks, {
|
58
|
-
|
71
|
+
my_app_network: {
|
59
72
|
connections: [
|
60
73
|
"nginx_reverse_proxy",
|
61
74
|
"nginx-proxy-letsencrypt",
|
@@ -65,28 +78,28 @@ set :shiplane_networks, {
|
|
65
78
|
|
66
79
|
set :shiplane_containers, {
|
67
80
|
app: {
|
68
|
-
alias: '
|
81
|
+
alias: 'my_app-app',
|
69
82
|
volumes: [],
|
70
83
|
environment: [],
|
71
84
|
expose: 3000,
|
72
85
|
capistrano_role: "docker",
|
73
|
-
repo: "
|
86
|
+
repo: "my_docker_repo_account_name/my_docker_repo",
|
74
87
|
command: 'bin/start',
|
75
|
-
virtual_host: "
|
76
|
-
letsencrypt_email: "john.epperson@
|
88
|
+
virtual_host: "my_app.com",
|
89
|
+
letsencrypt_email: "john.epperson@my_app_network.com",
|
77
90
|
networks: [
|
78
|
-
"
|
91
|
+
"my_app_network"
|
79
92
|
],
|
80
93
|
},
|
81
94
|
sidekiq: {
|
82
|
-
alias: '
|
95
|
+
alias: 'my_app-sidekiq',
|
83
96
|
volumes: [],
|
84
97
|
environment: [],
|
85
98
|
capistrano_role: "docker",
|
86
|
-
repo: "
|
99
|
+
repo: "my_docker_repo_account_name/my_docker_repo",
|
87
100
|
command: 'bin/start_sidekiq_workers',
|
88
101
|
networks: [
|
89
|
-
"
|
102
|
+
"my_app_network"
|
90
103
|
],
|
91
104
|
},
|
92
105
|
redis: {
|
@@ -101,22 +114,37 @@ set :shiplane_containers, {
|
|
101
114
|
repo: "redis",
|
102
115
|
tag: "4.0.9-alpine",
|
103
116
|
networks: [
|
104
|
-
"
|
117
|
+
"my_app_network"
|
105
118
|
],
|
119
|
+
deploy: {
|
120
|
+
# This setting will tell shiplane NOT to restart this container every deploy
|
121
|
+
restart: false,
|
122
|
+
},
|
106
123
|
},
|
107
124
|
postgres: {
|
108
125
|
alias: 'postgres',
|
109
126
|
volumes: [
|
110
127
|
"/var/lib/postgres/data:/var/lib/postgresql/data",
|
111
128
|
],
|
129
|
+
environment: {
|
130
|
+
POSTGRES_PASSWORD: ENV['DATABASE_PASSWORD'],
|
131
|
+
POSTGRES_USER: ENV['DATABASE_USERNAME'],
|
132
|
+
},
|
133
|
+
flags: {
|
134
|
+
'shm-size' => '256MB',
|
135
|
+
},
|
112
136
|
expose: 5432,
|
113
137
|
publish: 5432,
|
114
138
|
capistrano_role: "docker",
|
115
139
|
repo: "postgres",
|
116
140
|
tag: "9.6",
|
117
141
|
networks: [
|
118
|
-
"
|
142
|
+
"my_app_network"
|
119
143
|
],
|
144
|
+
deploy: {
|
145
|
+
# This setting will tell shiplane NOT to restart this container every deploy
|
146
|
+
restart: false,
|
147
|
+
},
|
120
148
|
},
|
121
149
|
}
|
122
150
|
|
@@ -6,6 +6,8 @@ COPY . $APP_PATH
|
|
6
6
|
|
7
7
|
WORKDIR $APP_PATH
|
8
8
|
|
9
|
+
ARG RAILS_ENV production
|
10
|
+
ENV RAILS_ENV $RAILS_ENV
|
9
11
|
ARG GITHUB_TOKEN
|
10
12
|
ENV GITHUB_TOKEN $GITHUB_TOKEN
|
11
13
|
ENV SHIPLANE building
|
@@ -36,7 +38,8 @@ WORKDIR $APP_PATH
|
|
36
38
|
RUN bundle config --local path vendor/bundle
|
37
39
|
RUN bundle config --local without development:test:assets
|
38
40
|
|
39
|
-
|
41
|
+
ARG RAILS_ENV production
|
42
|
+
ENV RAILS_ENV $RAILS_ENV
|
40
43
|
ENV SHIPLANE running
|
41
44
|
ENV RAILS_LOG_TO_STDOUT true
|
42
45
|
ENV RAILS_SERVE_STATIC_FILES true
|
@@ -6,9 +6,12 @@ project:
|
|
6
6
|
bootstrap:
|
7
7
|
env_file: .env.production
|
8
8
|
chef-bootstrapper:
|
9
|
-
package_name: chefdk_3.
|
10
|
-
package_url: https://packages.chef.io/files/stable/chefdk/3.
|
9
|
+
package_name: chefdk_3.13.1-1_amd64.deb
|
10
|
+
package_url: https://packages.chef.io/files/stable/chefdk/3.13.1/ubuntu/16.04/chefdk_3.13.1-1_amd64.deb
|
11
11
|
build:
|
12
|
+
registry:
|
13
|
+
# url: ghcr.io # for Github Container Service. Should work for similar services such as Gitlab
|
14
|
+
# url: :dockerhub # for default Dockerhub Service
|
12
15
|
settings_folder: .shiplane
|
13
16
|
environment_file: .env.production
|
14
17
|
compose_filepath: docker-compose.yml
|
@@ -31,9 +34,9 @@ build:
|
|
31
34
|
- services.container-name.depends_on
|
32
35
|
deploy:
|
33
36
|
servers:
|
34
|
-
#
|
37
|
+
# `server-url` is your server domain or ip address here (e.g. `12.345.67.89` or `myapp.com`)
|
35
38
|
server-url:
|
36
|
-
# Only set this flag if you need docker to run as
|
39
|
+
# Only set this flag if you need docker to escalate permissions and run everything as sudo (default Ubuntu AMI on AWS requires this)
|
37
40
|
requires_sudo: false
|
38
41
|
# Put SSH options here
|
39
42
|
# ssh_options:
|
data/lib/shiplane/build.rb
CHANGED
@@ -19,8 +19,99 @@ module Shiplane
|
|
19
19
|
@postfix = postfix
|
20
20
|
|
21
21
|
Dotenv.overload File.join(Dir.pwd, build_config.fetch('environment_file', '.env'))
|
22
|
+
|
23
|
+
# Add any ENV variable overrides from the capistrano configuration
|
24
|
+
environment_variable_overrides = fetch(:shiplane_build_environment_variables, {})
|
25
|
+
environment_variable_overrides.each do |key, value|
|
26
|
+
if value.is_a? Proc
|
27
|
+
ENV[key.to_s] = value.call
|
28
|
+
else
|
29
|
+
ENV[key.to_s] = value
|
30
|
+
end
|
31
|
+
end
|
22
32
|
end
|
23
33
|
|
34
|
+
def build!
|
35
|
+
unless File.exist?(File.join(project_folder, Shiplane::SHIPLANE_CONFIG_FILENAME))
|
36
|
+
Shiplane::CheckoutArtifact.checkout!(sha)
|
37
|
+
Shiplane::ConvertComposeFile.convert_output!(project_folder, sha)
|
38
|
+
end
|
39
|
+
|
40
|
+
buildable_artifacts.each do |(artifact_name, attributes)|
|
41
|
+
compose_context = docker_config.fetch('services', {}).fetch(artifact_name.to_s, {})
|
42
|
+
Shiplane::ConvertDockerfile.convert_output!(project_folder, attributes, compose_context)
|
43
|
+
|
44
|
+
FileUtils.cd project_folder do
|
45
|
+
steps(artifact_name, attributes).select{|step| step.fetch(:condition, true) }.each do |step|
|
46
|
+
puts step[:notify_before] if step.has_key? :notify_before
|
47
|
+
success = system(step[:command])
|
48
|
+
raise StepFailureException.new(step[:command], artifact_name) unless success
|
49
|
+
puts step[:notify_after] if step.has_key? :notify_after
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue StepFailureException => e
|
54
|
+
puts e.message
|
55
|
+
raise if ENV['RAISE_EXCEPTIONS_ON_FAILED_BUILD']
|
56
|
+
end
|
57
|
+
|
58
|
+
def steps(artifact_name, attributes)
|
59
|
+
[
|
60
|
+
{ command: build_command(artifact_name), notify_before: "Building Artifact: #{artifact_name}...", notify_after: "Docker Compose Built", stop_on_failure: true },
|
61
|
+
{ command: tag_command(artifact_name, attributes, sha), notify_before: "Tagging Build [#{sha}]...", stop_on_failure: true },
|
62
|
+
{ command: tag_command(artifact_name, attributes, "#{postfix}-#{sha}"), notify_before: "Tagging Build [#{postfix}-#{sha}]...", stop_on_failure: true, condition: !!postfix },
|
63
|
+
{ command: tag_command(artifact_name, attributes, "#{postfix}-latest"), notify_before: "Tagging Build [#{postfix}-latest]...", stop_on_failure: true, condition: !!postfix && tag_latest },
|
64
|
+
{ command: tag_command(artifact_name, attributes), notify_before: "Tagging Build [latest]...", stop_on_failure: true, condition: tag_latest },
|
65
|
+
{ command: token_login_command , notify_before: "Logging into Container Registry...", stop_on_failure: true },
|
66
|
+
{ command: push_command(attributes, "#{sha}"), notify_before: "Pushing Image", notify_after: "Completed Artifact: #{artifact_name}...", stop_on_failure: true },
|
67
|
+
{ command: push_command(attributes, "#{postfix}-#{sha}"), notify_before: "Pushing #{postfix} Image", notify_after: "Completed Artifact: #{artifact_name}...", stop_on_failure: true, condition: !!postfix },
|
68
|
+
{ command: push_command(attributes, "#{postfix}-latest"), notify_before: "Pushing Latest #{postfix} Image", notify_after: "Completed Latest Artifact: #{artifact_name}...", stop_on_failure: true, condition: !!postfix && tag_latest },
|
69
|
+
{ command: push_command(attributes, "latest"), notify_before: "Pushing Latest Image", notify_after: "Completed Latest Artifact: #{artifact_name}...", stop_on_failure: true, condition: tag_latest },
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Commands
|
74
|
+
def build_command(artifact_name)
|
75
|
+
[
|
76
|
+
'docker-compose',
|
77
|
+
'build',
|
78
|
+
build_cache_option,
|
79
|
+
artifact_name,
|
80
|
+
].compact.join(' ')
|
81
|
+
end
|
82
|
+
|
83
|
+
def token_login_command
|
84
|
+
@token_login_command ||= [
|
85
|
+
'echo',
|
86
|
+
"\"#{login_token}\"",
|
87
|
+
'|',
|
88
|
+
'docker',
|
89
|
+
'login',
|
90
|
+
registry_url,
|
91
|
+
'--username',
|
92
|
+
login_username,
|
93
|
+
'--password-stdin',
|
94
|
+
].compact.join(' ')
|
95
|
+
end
|
96
|
+
|
97
|
+
def tag_command(artifact_name, attributes, tag='latest')
|
98
|
+
[
|
99
|
+
'docker',
|
100
|
+
'tag',
|
101
|
+
build_output_image_name(artifact_name),
|
102
|
+
"#{repo_name(attributes)}:#{tag}",
|
103
|
+
].compact.join(' ')
|
104
|
+
end
|
105
|
+
|
106
|
+
def push_command(attributes, tag='latest')
|
107
|
+
[
|
108
|
+
'docker',
|
109
|
+
'push',
|
110
|
+
"#{repo_name(attributes)}:#{tag}",
|
111
|
+
].compact.join(' ')
|
112
|
+
end
|
113
|
+
|
114
|
+
# Properties
|
24
115
|
def appname
|
25
116
|
@appname ||= project_config['appname']
|
26
117
|
end
|
@@ -45,44 +136,57 @@ module Shiplane
|
|
45
136
|
build_config.fetch('artifacts', {})
|
46
137
|
end
|
47
138
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
139
|
+
def default_registry_configuration
|
140
|
+
{
|
141
|
+
'url' => :dockerhub,
|
142
|
+
'auth_method' => 'token',
|
143
|
+
}
|
144
|
+
end
|
53
145
|
|
54
|
-
|
55
|
-
|
56
|
-
|
146
|
+
def dockerhub?
|
147
|
+
registry_configuration['url'] == :dockerhub
|
148
|
+
end
|
57
149
|
|
58
|
-
|
59
|
-
|
60
|
-
puts step[:notify_before] if step.has_key? :notify_before
|
61
|
-
success = system(step[:command])
|
62
|
-
raise StepFailureException.new(step[:command], artifact_name) unless success
|
63
|
-
puts step[:notify_after] if step.has_key? :notify_after
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
rescue StepFailureException => e
|
68
|
-
puts e.message
|
69
|
-
raise if ENV['RAISE_EXCEPTIONS_ON_FAILED_BUILD']
|
150
|
+
def token_auth?
|
151
|
+
registry_configuration['auth_method'] == 'token'
|
70
152
|
end
|
71
153
|
|
72
|
-
def
|
154
|
+
def registry_configuration
|
155
|
+
@registry_configuration ||= default_registry_configuration.merge(build_config.fetch('registry', {}))
|
156
|
+
end
|
157
|
+
|
158
|
+
def registry_url
|
159
|
+
@registry_url ||= dockerhub? ? nil : registry_configuration['url']
|
160
|
+
end
|
161
|
+
|
162
|
+
def repo_name(attributes)
|
73
163
|
[
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
164
|
+
registry_url,
|
165
|
+
attributes['repo'],
|
166
|
+
].compact.join('/')
|
167
|
+
end
|
168
|
+
|
169
|
+
def login_token
|
170
|
+
return ENV['DOCKERHUB_PASSWORD'] if dockerhub? && token_auth?
|
171
|
+
|
172
|
+
ENV['SHIPLANE_CONTAINER_REGISTRY_TOKEN']
|
173
|
+
end
|
174
|
+
|
175
|
+
def login_username
|
176
|
+
return ENV['DOCKERHUB_USERNAME'] if dockerhub? && token_auth?
|
177
|
+
|
178
|
+
ENV['SHIPLANE_CONTAINER_REGISTRY_USERNAME']
|
179
|
+
end
|
180
|
+
|
181
|
+
def build_output_image_name(artifact_name)
|
182
|
+
@build_output_image_name ||= "#{appname}-#{sha}_#{artifact_name}:latest"
|
183
|
+
end
|
184
|
+
|
185
|
+
def build_cache_option
|
186
|
+
ENV['USE_BUILD_CACHE'] == 'true' ? nil : "--no-cache"
|
84
187
|
end
|
85
188
|
|
189
|
+
# API Helper Methods
|
86
190
|
def self.build!(sha, postfix = nil)
|
87
191
|
new(sha, postfix: postfix).build!
|
88
192
|
end
|
@@ -32,10 +32,6 @@ module Shiplane
|
|
32
32
|
|
33
33
|
def converted_output
|
34
34
|
@converted_output ||= converted_compose_hash.dup.tap do |hash|
|
35
|
-
build_config.fetch('artifacts', {}).each do |(appname, config)|
|
36
|
-
hash.deep_merge!({ 'services' => { appname => { 'image' => "#{config['repo']}:#{sha}" } } })
|
37
|
-
end
|
38
|
-
|
39
35
|
hash.traverse! do |key, value|
|
40
36
|
if (key == 'env_file' && value == '.env.development')
|
41
37
|
[key, '.env.production']
|
@@ -64,7 +64,7 @@ module Shiplane
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def network_connect_commands(role)
|
67
|
-
@network_commands ||= networks.map do |network|
|
67
|
+
@network_commands ||= networks[1..-1].map do |network|
|
68
68
|
[
|
69
69
|
docker_command(role),
|
70
70
|
"network connect",
|
@@ -84,6 +84,8 @@ module Shiplane
|
|
84
84
|
published_ports.map{|port| "-p #{port}" },
|
85
85
|
exposed_ports.map{|port| "--expose #{port}" },
|
86
86
|
"--name #{unique_container_name}",
|
87
|
+
"--network=#{networks.first}",
|
88
|
+
"--network-alias=#{network_alias}",
|
87
89
|
virtual_host ? "-e VIRTUAL_HOST=#{virtual_host}" : nil,
|
88
90
|
letsencrypt_host ? "-e LETSENCRYPT_HOST=#{letsencrypt_host}" : nil,
|
89
91
|
letsencrypt_email ? "-e LETSENCRYPT_EMAIL=#{letsencrypt_email}" : nil,
|
data/lib/shiplane/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shiplane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Epperson
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: shiplane_bootstrappers_chef
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.2.4
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.2.4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: shiplane_deployers_capistrano_docker
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - '='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.2.4
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.2.4
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: dotenv
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,7 +156,7 @@ homepage: https://github.com/kirillian/shiplane
|
|
156
156
|
licenses:
|
157
157
|
- MIT
|
158
158
|
metadata: {}
|
159
|
-
post_install_message:
|
159
|
+
post_install_message:
|
160
160
|
rdoc_options: []
|
161
161
|
require_paths:
|
162
162
|
- lib
|
@@ -172,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
172
|
version: '0'
|
173
173
|
requirements: []
|
174
174
|
rubygems_version: 3.0.3
|
175
|
-
signing_key:
|
175
|
+
signing_key:
|
176
176
|
specification_version: 4
|
177
177
|
summary: A toolbox for converting developer docker-compose files into production-ready
|
178
178
|
images.
|