dockside 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bd58f05fb82181007e8da7a9f49157d2f041b1f8596aac39925b8d479497180b
4
+ data.tar.gz: fe072118357dbf60858c1a1ea49a27625ec89ea888591c93531729ce68c4b1af
5
+ SHA512:
6
+ metadata.gz: f6d35e9aeca9dca089ef21dd794d2bdbe8d62990b9459f46c4bb01a3ea34639d9613c05f05703b08f5d6b80293ba86c351e1111ab0ef0d8bf9365dc32f86020c
7
+ data.tar.gz: fc9b56bff1b170c052b7b32ccb77db2804e928585ffd478ca9218171da59ede6f65a028827e7b0528dbb8e0c9c7c85b7633a09b3ae5b8cc6448f2f21ae999e47
data/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Semaphore CI pipeline with a manual promotion to release the gem.
13
+
14
+ ## [0.1.0] - 2026-09-22
15
+
16
+ ### Added
17
+
18
+ - Compose-file based configuration of the containers a Rails app needs.
19
+ - Automatic start of the containers in development and test.
20
+ - Readiness probes that wait until the containers are ready.
21
+ - `after_start` hooks to provision containers the first time they start.
22
+ - Reset of the containers and their data.
23
+ - Rake tasks to start, stop and reset the containers.
24
+ - Install generator.
25
+
26
+ [Unreleased]: https://github.com/coorasse/dockside/compare/v0.1.0...HEAD
27
+ [0.1.0]: https://github.com/coorasse/dockside/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Renuo AG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,479 @@
1
+ # dockside
2
+
3
+ [![Build Status](https://coorasse.semaphoreci.com/badges/dockside/branches/main.svg)](https://coorasse.semaphoreci.com/projects/dockside)
4
+ [![Gem Version](https://badge.fury.io/rb/dockside.svg)](https://rubygems.org/gems/dockside)
5
+
6
+ Starts the Docker containers your Rails app needs, when your app starts.
7
+
8
+ ## The problem
9
+
10
+ Your app uses services that are not part of your app. An S3 bucket for uploads. A search engine. A login server.
11
+ In production these are hosted somewhere. On your machine, you run them in Docker.
12
+
13
+ Docker Compose describes the containers. But it does not start them for you. Before you run the server or the
14
+ tests, you have to remember to run `docker compose up`. Then you wait until the service really answers.
15
+ And if the server and the tests share a container, the tests wipe the data you were looking at.
16
+
17
+ ## dockside to the rescue
18
+
19
+ You describe the containers in a compose file. The gem does the rest:
20
+
21
+ - starts the containers when you run `rails server` or the test suite
22
+ - waits until they are ready
23
+ - sets them up the first time (creates the bucket, loads the data)
24
+ - keeps one set of containers for development and another for test
25
+ - does nothing when they are already running
26
+
27
+ ```yaml
28
+ # config/dockside.yml
29
+ services:
30
+ minio:
31
+ image: minio/minio
32
+ command: server /data
33
+ ports: ["9000:9000"]
34
+ x-dockside:
35
+ test:
36
+ ports: ["9010:9000"]
37
+ ```
38
+
39
+ This is a normal compose file that follows the
40
+ [Compose Specification](https://docs.docker.com/reference/compose-file/). The gem only reads the `x-dockside`
41
+ part, which is an [extension field](https://docs.docker.com/reference/compose-file/extension/). Compose ignores
42
+ extension fields, so `docker compose` can read the same file. See
43
+ [Using plain docker compose](#using-plain-docker-compose).
44
+
45
+ ## Installation
46
+
47
+ ```ruby
48
+ # Gemfile
49
+ group :development, :test do
50
+ gem "dockside"
51
+ end
52
+ ```
53
+
54
+ ```sh
55
+ bundle install
56
+ bin/rails generate dockside:install
57
+ ```
58
+
59
+ You need Docker with `docker compose`.
60
+
61
+ ## Example: an S3 bucket for Active Storage
62
+
63
+ [MinIO](https://min.io) is an S3 server that runs in one container. Here is the compose file. The two `exec` lines
64
+ create the bucket after the container starts:
65
+
66
+ ```yaml
67
+ # config/dockside.yml
68
+ services:
69
+ minio:
70
+ image: minio/minio
71
+ command: server /data
72
+ environment:
73
+ MINIO_ROOT_USER: minio
74
+ MINIO_ROOT_PASSWORD: minio-secret
75
+ ports: ["9000:9000"]
76
+ x-dockside:
77
+ after_start:
78
+ - exec: mc alias set local http://localhost:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
79
+ - exec: mc mb --ignore-existing local/uploads
80
+ test:
81
+ ports: ["9010:9000"]
82
+ ```
83
+
84
+ Tell Active Storage where to find it. The gem does not do this for you. Your app keeps its own settings:
85
+
86
+ ```yaml
87
+ # config/storage.yml
88
+ minio:
89
+ service: S3
90
+ endpoint: http://localhost:9000
91
+ access_key_id: minio
92
+ secret_access_key: minio-secret
93
+ region: us-east-1
94
+ bucket: uploads
95
+ force_path_style: true
96
+
97
+ minio_test:
98
+ service: S3
99
+ endpoint: http://localhost:9010
100
+ access_key_id: minio
101
+ secret_access_key: minio-secret
102
+ region: us-east-1
103
+ bucket: uploads
104
+ force_path_style: true
105
+ ```
106
+
107
+ ```ruby
108
+ # config/environments/development.rb
109
+ config.active_storage.service = :minio
110
+
111
+ # config/environments/test.rb
112
+ config.active_storage.service = :minio_test
113
+ ```
114
+
115
+ Now start the server:
116
+
117
+ ```
118
+ $ bin/rails server
119
+ dockside: starting minio (development)
120
+ ✔ Container my-app-development-minio-1 Started
121
+ dockside: minio ready at http://localhost:9000
122
+ => Booting Puma
123
+ ```
124
+
125
+ Or run the tests:
126
+
127
+ ```
128
+ $ bundle exec rspec
129
+ dockside: starting minio (test)
130
+ ✔ Container my-app-test-minio-1 Started
131
+ dockside: minio ready at http://localhost:9010
132
+ ```
133
+
134
+ Next time, the container is already there:
135
+
136
+ ```
137
+ dockside: minio already running at http://localhost:9000
138
+ ```
139
+
140
+ ## Development and test
141
+
142
+ The server and the tests often run at the same time. Usually you want each one to have its own container, so
143
+ the tests cannot touch the data you are looking at in the browser. Two containers cannot use the same port on
144
+ your machine. That is why the `test:` block gives the test container another port.
145
+
146
+ Everything outside `x-dockside` is normal compose and is the same in every environment. The `test:`
147
+ block (or a `development:` block) holds only what is different. You can put any
148
+ [service setting](https://docs.docker.com/reference/compose-file/services/) in it.
149
+
150
+ Here, development keeps the uploaded files on disk. Test starts with an empty store every time:
151
+
152
+ ```yaml
153
+ services:
154
+ minio:
155
+ image: minio/minio
156
+ command: server /data
157
+ ports: ["9000:9000"]
158
+ volumes:
159
+ - ./tmp/dockside/minio:/data
160
+ x-dockside:
161
+ test:
162
+ ports: ["9010:9000"]
163
+ volumes: []
164
+ ```
165
+
166
+ A list inside the block replaces the list outside. The test container only has port 9010, and no volumes.
167
+
168
+ ### One container for both
169
+
170
+ You do not have to split them. Leave the `test:` block out and development and test share one container:
171
+
172
+ ```yaml
173
+ services:
174
+ mailpit:
175
+ image: axllent/mailpit
176
+ ports: ["8025:8025", "1025:1025"]
177
+ ```
178
+
179
+ The gem prints a warning when it loads the file, because the two environments now see the same data. Whoever
180
+ starts first creates the container. The other environment finds the port answered by a container of the same
181
+ app and uses it instead of starting a second one. `after_start` runs once, on whoever created it. A reset from
182
+ either environment removes the container for both.
183
+
184
+ This is fine for services without state, such as a mail catcher or a renderer. For a database or an S3 store,
185
+ give test its own port.
186
+
187
+ ## Building your own image
188
+
189
+ If the service needs your own Dockerfile, use [`build`](https://docs.docker.com/reference/compose-file/build/)
190
+ like in any compose file:
191
+
192
+ ```yaml
193
+ services:
194
+ keycloak:
195
+ build:
196
+ context: .
197
+ dockerfile: Dockerfile.keycloak
198
+ ports: ["8080:8080"]
199
+ x-dockside:
200
+ test:
201
+ ports: ["8081:8080"]
202
+ ```
203
+
204
+ You can also build from a git URL:
205
+
206
+ ```yaml
207
+ services:
208
+ renderer:
209
+ build: https://github.com/my-org/renderer.git#main
210
+ ```
211
+
212
+ The image is built the first time. Development and test share it. To build it again:
213
+
214
+ ```sh
215
+ BUILD=1 bin/rails dockside:up[keycloak]
216
+ ```
217
+
218
+ ## Waiting until ready
219
+
220
+ The gem waits until the container runs and its first port accepts a connection that stays open. (Docker accepts
221
+ connections on a published port before the service inside listens, but closes them at once.) For most services
222
+ that is enough.
223
+
224
+ Some services open the port before they are ready to answer. Then tell the gem what to check:
225
+
226
+ ```yaml
227
+ services:
228
+ minio:
229
+ image: minio/minio
230
+ command: server /data
231
+ ports: ["9000:9000"]
232
+ x-dockside:
233
+ ready: { http: "/minio/health/live" }
234
+ timeout: 60
235
+ test:
236
+ ports: ["9010:9000"]
237
+ ```
238
+
239
+ You can wait for:
240
+
241
+ - `{ http: "/path" }`: the URL answers. Add `status: 200` if the status matters.
242
+ - `{ log: "some text" }`: the container log contains the text (a regular expression).
243
+ - `{ command: ["sh", "-c", "..."] }`: the command succeeds inside the container.
244
+ - `none`: do not wait for anything beyond the container running.
245
+
246
+ `timeout` is in seconds and defaults to 300. When time runs out, the gem raises an error with the last lines of
247
+ the container log.
248
+
249
+ ## Keeping data
250
+
251
+ [Volumes](https://docs.docker.com/reference/compose-file/services/#volumes) work like in compose. Use a folder
252
+ under `tmp/` so the data survives a restart but is not committed:
253
+
254
+ ```yaml
255
+ services:
256
+ minio:
257
+ image: minio/minio
258
+ command: server /data
259
+ ports: ["9000:9000"]
260
+ volumes:
261
+ - ./tmp/dockside/${RAILS_ENV}/minio:/data
262
+ x-dockside:
263
+ test:
264
+ ports: ["9010:9000"]
265
+ ```
266
+
267
+ The gem sets `RAILS_ENV` for compose, so
268
+ [interpolation](https://docs.docker.com/reference/compose-file/interpolation/) gives development and test their
269
+ own folder from this one line. The gem also creates the folder before the container starts.
270
+
271
+ ## Setting up the container
272
+
273
+ A new container is usually empty. The bucket is missing, the users are missing, the data is missing.
274
+ `after_start` lists the commands that fix that. The gem runs them once, after the container is ready:
275
+
276
+ ```yaml
277
+ services:
278
+ minio:
279
+ image: minio/minio
280
+ command: server /data
281
+ environment:
282
+ MINIO_ROOT_USER: minio
283
+ MINIO_ROOT_PASSWORD: minio-secret
284
+ ports: ["9000:9000"]
285
+ x-dockside:
286
+ after_start:
287
+ - exec: mc alias set local http://localhost:9000 $MINIO_ROOT_USER $MINIO_ROOT_PASSWORD
288
+ - exec: mc mb --ignore-existing local/uploads
289
+ - exec: mc admin policy create local uploads /dev/stdin
290
+ stdin: config/minio/uploads_policy.json
291
+ allow_failure: true
292
+ test:
293
+ ports: ["9010:9000"]
294
+ ```
295
+
296
+ - `exec:` runs a command inside the container.
297
+ - `run:` runs a command on your machine, in the app folder. Use it for Ruby scripts, for example
298
+ `run: bin/rails runner script/seed_minio.rb`.
299
+ - `stdin:` sends a file from your app into the command.
300
+ - `allow_failure: true` moves on when the command fails.
301
+ - `always: true` runs the command on every start, not only the first time.
302
+ - `environment:`, `workdir:` and `user:` do what they do in compose.
303
+
304
+ Commands inside the container see the environment variables of the service. That is why `$MINIO_ROOT_USER` works
305
+ above. Every command also gets `DOCKSIDE_PORT`, `DOCKSIDE_URL` and `DOCKSIDE_CONTAINER`,
306
+ so a script knows which container it is talking to.
307
+
308
+ "Once" means once per container. Restarting the same container does not run the commands again. A new container
309
+ (after a config change, a rebuild or a reset) does.
310
+
311
+ ## Starting over
312
+
313
+ When the data in a container is a mess, reset it:
314
+
315
+ ```sh
316
+ bin/rails dockside:reset[minio]
317
+ RAILS_ENV=test bin/rails dockside:reset[minio]
318
+ ```
319
+
320
+ This removes the container, its volumes and everything in its data folders under `tmp/`, then starts it again.
321
+ The `after_start` commands run again on the empty container.
322
+
323
+ ## Running a command in a container from Ruby
324
+
325
+ ```ruby
326
+ minio = Dockside.minio
327
+ minio.exec("mc rm --recursive --force local/uploads")
328
+ ```
329
+
330
+ `exec` returns the output. If the command fails, it raises an error. It takes the same options as `after_start`:
331
+ `environment:`, `stdin:` (a string here), `workdir:`, `user:`, `allow_failure:`.
332
+
333
+ ## When containers start
334
+
335
+ - In development: when you run `rails server`. The console, `rails runner` and rake tasks do not start anything.
336
+ - In test: before the first test, with RSpec or Minitest. You do not need to add anything to your test setup.
337
+
338
+ The app waits until every container is ready. In test, the containers start even if no test needs them.
339
+
340
+ To keep a container out of this, set `autostart: false`. Then it only starts when you ask:
341
+
342
+ ```yaml
343
+ services:
344
+ keycloak:
345
+ image: quay.io/keycloak/keycloak:26.0
346
+ command: start-dev
347
+ ports: ["8080:8080"]
348
+ x-dockside:
349
+ autostart: false
350
+ test:
351
+ ports: ["8081:8080"]
352
+ ```
353
+
354
+ ```sh
355
+ bin/rails dockside:up[keycloak]
356
+ ```
357
+
358
+ ```ruby
359
+ Dockside.ensure_running!(:keycloak)
360
+ ```
361
+
362
+ To skip all of it for one run, for example because the service is already running somewhere else:
363
+
364
+ ```sh
365
+ DOCKSIDE_AUTOSTART=0 bin/rails server
366
+ ```
367
+
368
+ ## Rake tasks
369
+
370
+ Use `RAILS_ENV=test` in front to work on the test containers.
371
+
372
+ | Task | What it does |
373
+ |---|---|
374
+ | `dockside:up[names]` | Start the containers. `BUILD=1` rebuilds, `PULL=1` pulls. |
375
+ | `dockside:stop[names]` | Stop the containers, keep the data. |
376
+ | `dockside:down` | Remove the containers, keep the data. |
377
+ | `dockside:reset[names]` | Remove the containers and the data, start again. |
378
+ | `dockside:status` | Show every container, its state and URL. |
379
+ | `dockside:logs[name]` | Show the container log. `TAIL=200`, `FOLLOW=1`. |
380
+ | `dockside:config` | Show the compose file the gem really uses. |
381
+
382
+ Separate names with commas: `dockside:up[minio,keycloak]`.
383
+
384
+ ## Ruby API
385
+
386
+ You will rarely need it. It is there for seeds, scripts and test helpers.
387
+
388
+ ```ruby
389
+ Dockside.names # => [:minio, :keycloak]
390
+ Dockside.minio # one dependency, for the current environment
391
+ Dockside.fetch(:minio) # the same, for a name you only have as a variable
392
+
393
+ Dockside.ensure_running! # start everything with autostart: true
394
+ Dockside.ensure_running!(:minio) # start one
395
+ Dockside.down # remove the containers of the current environment
396
+ ```
397
+
398
+ ```ruby
399
+ minio.port # => 9010
400
+ minio.url # => "http://localhost:9010"
401
+ minio.container_name # => "my-app-test-minio-1"
402
+ minio.running?
403
+ minio.ready?
404
+ minio.start
405
+ minio.stop
406
+ minio.reset
407
+ minio.exec("mc ls local/uploads")
408
+ minio.logs(tail: 100)
409
+ ```
410
+
411
+ All errors inherit from `Dockside::Error`. The message always says what went wrong and, when a
412
+ container is involved, includes its last log lines.
413
+
414
+ ## Good to know
415
+
416
+ - Containers are named `<app>-<environment>-<service>-1`. Do not set `container_name` yourself.
417
+ - Paths in the compose file are relative to the app folder.
418
+ - `host.docker.internal` works inside every container, also on Linux.
419
+ - The gem writes its own files to `tmp/dockside/`.
420
+ - Two checkouts of the same app share the same containers and ports.
421
+ - Ports and passwords appear twice: in the compose file and in your app's config. That is on purpose. The gem
422
+ never changes your app's config.
423
+ - `dockside:config` shows the exact compose file. You can always run `docker compose` yourself with it.
424
+
425
+ ## Using plain docker compose
426
+
427
+ `config/dockside.yml` is a valid compose file. Nothing stops you from using it without the gem, for example
428
+ from a shell, in CI, or with a tool that is not Rails.
429
+
430
+ For development, point compose at the file and use the same project name as the gem. Then compose sees the
431
+ same containers the gem started, and the gem sees the ones compose started:
432
+
433
+ ```sh
434
+ docker compose -p my-app-development -f config/dockside.yml up -d
435
+ docker compose -p my-app-development -f config/dockside.yml ps
436
+ docker compose -p my-app-development -f config/dockside.yml logs -f minio
437
+ docker compose -p my-app-development -f config/dockside.yml down
438
+ ```
439
+
440
+ The project name is `<app>-<environment>`. That is where the container names `my-app-development-minio-1` come
441
+ from. If the file uses `${RAILS_ENV}`, set it before you run compose: `RAILS_ENV=development docker compose ...`.
442
+ Compose also interpolates `$VARIABLES` inside `x-dockside`, so an `after_start` command like
443
+ `mc alias set local http://localhost:9000 $MINIO_ROOT_USER ...` makes it print a warning. The warning is harmless;
444
+ the gem reads the commands itself, from the original file.
445
+
446
+ Compose does not apply the `test:` overrides. It only sees the `x-dockside` block as an extension field it
447
+ does not understand. To get the file for one environment with the overrides merged in, let the gem print it:
448
+
449
+ ```sh
450
+ RAILS_ENV=test bin/rails dockside:config > tmp/dockside/compose.test.yml
451
+ docker compose -p my-app-test -f tmp/dockside/compose.test.yml up -d
452
+ ```
453
+
454
+ The printed file has no `x-dockside` part left, so it can go into any compose command, a `docker-compose.yml`
455
+ in another repository, or a CI step that has no Ruby.
456
+
457
+ Three things stay with the gem and do not happen when you use compose alone:
458
+
459
+ - waiting until the service is ready (`ready:` and `timeout:`)
460
+ - the `after_start` commands
461
+ - creating the `tmp/` folders for volumes
462
+
463
+ With compose alone, run those steps yourself, for example with `docker compose exec minio mc mb local/uploads`.
464
+
465
+ ## Development
466
+
467
+ ```sh
468
+ bin/setup
469
+ bin/check # the specs; they need the docker CLI but no running daemon
470
+ DOCKER=1 bin/check # also runs the specs against a real Docker
471
+ bin/fastcheck # standardrb
472
+ ```
473
+
474
+ The specs talk to a fake Docker (`spec/support/fake_docker.rb`) that remembers which containers compose
475
+ would have started. Only `docker compose config` reaches the real compose CLI, because it just parses files.
476
+
477
+ ## License
478
+
479
+ MIT
@@ -0,0 +1,33 @@
1
+ module Dockside
2
+ # Decides when the containers start on their own: with `rails server` in development,
3
+ # before the first test in test.
4
+ module Autostart
5
+ def self.enabled?(config)
6
+ return config.autostart unless config.autostart.nil?
7
+
8
+ ENV["DOCKSIDE_AUTOSTART"] != "0"
9
+ end
10
+
11
+ def self.boot(config, env:, server_process:)
12
+ install(env: env, server_process: server_process) if enabled?(config)
13
+ end
14
+
15
+ # rspec-rails defines RSpec in every process; only an rspec run loads RSpec.configure.
16
+ def self.install(env:, server_process:, rspec: (::RSpec if defined?(::RSpec) && ::RSpec.respond_to?(:configure)))
17
+ case env.to_s
18
+ when "development"
19
+ Dockside.ensure_running! if server_process
20
+ when "test"
21
+ install_test_hook(rspec)
22
+ end
23
+ end
24
+
25
+ def self.install_test_hook(rspec)
26
+ if rspec
27
+ rspec.configure { |config| config.before(:suite) { Dockside.ensure_running! } }
28
+ else
29
+ ActiveSupport.on_load(:active_support_test_case) { Dockside.ensure_running! }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ module Dockside
2
+ # What the rake tasks do, without rake.
3
+ module Commands
4
+ module_function
5
+
6
+ def up(names, build: false, pull: false)
7
+ select(names).each { |dependency| dependency.start(build: build, pull: pull) }
8
+ end
9
+
10
+ def stop(names)
11
+ select(names).each do |dependency|
12
+ dependency.stop
13
+ Dockside.log "stopped #{dependency.name} (#{dependency.env})"
14
+ end
15
+ end
16
+
17
+ def down
18
+ Dockside.down
19
+ Dockside.log "removed the #{Dockside.env} containers"
20
+ end
21
+
22
+ def reset(names)
23
+ select(names).each(&:reset)
24
+ end
25
+
26
+ def status
27
+ Dockside.registry.each do |dependency|
28
+ Dockside.output.puts status_line(dependency)
29
+ end
30
+ end
31
+
32
+ def logs(name, tail: 100, follow: false)
33
+ dependency = Dockside.fetch(name)
34
+ Dockside.project.compose.logs(dependency.name, tail: tail, follow: follow).tap do |result|
35
+ Dockside.output.print result.stdout unless follow
36
+ end
37
+ end
38
+
39
+ def config
40
+ Dockside.output.print Dockside.project.resolved_yaml
41
+ end
42
+
43
+ def select(names)
44
+ names.empty? ? Dockside.registry.to_a : names.map { |name| Dockside.fetch(name) }
45
+ end
46
+
47
+ def status_line(dependency)
48
+ state = if !dependency.running?
49
+ "not running"
50
+ elsif dependency.ready?
51
+ "running, ready"
52
+ else
53
+ "running, not ready"
54
+ end
55
+ state = "#{state}, shared with #{dependency.project.other_env}" if dependency.shared?
56
+ autostart = dependency.autostart? ? "autostart" : "manual"
57
+ [dependency.name, dependency.container_name, state, dependency.url || "no port", autostart].join(" ")
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,68 @@
1
+ module Dockside
2
+ # Wraps `docker compose` for one project (one app in one environment).
3
+ class Compose
4
+ def initialize(project)
5
+ @project = project
6
+ end
7
+
8
+ def config
9
+ JSON.parse(run!(["config", "--format", "json"]).stdout)
10
+ end
11
+
12
+ def config_yaml
13
+ run!(["config"]).stdout
14
+ end
15
+
16
+ def up(service, build: false, pull: false, timeout: 300)
17
+ argv = ["up", "--detach", "--wait", "--wait-timeout", timeout.to_s]
18
+ argv << "--build" if build
19
+ argv += ["--pull", "always"] if pull
20
+ run(argv + [service], stream: true)
21
+ end
22
+
23
+ def stop(service)
24
+ run!(["stop", service])
25
+ end
26
+
27
+ def remove(service)
28
+ run!(["rm", "--stop", "--force", "--volumes", service])
29
+ end
30
+
31
+ def down
32
+ run!(["down"])
33
+ end
34
+
35
+ def exec(service, command, environment: {}, stdin: nil, workdir: nil, user: nil)
36
+ argv = ["exec", "--no-TTY"]
37
+ environment.each { |key, value| argv += ["--env", "#{key}=#{value}"] }
38
+ argv += ["--workdir", workdir] if workdir
39
+ argv += ["--user", user] if user
40
+ run(argv + [service] + shell_words(command), stdin: stdin)
41
+ end
42
+
43
+ def logs(service, tail: 100, follow: false)
44
+ argv = ["logs", "--no-color", "--tail", tail.to_s]
45
+ argv << "--follow" if follow
46
+ run(argv + [service], stream: follow)
47
+ end
48
+
49
+ def argv_prefix
50
+ ["docker", "compose", "--project-name", @project.name, "--project-directory", @project.root.to_s] +
51
+ @project.files.flat_map { |file| ["--file", file.to_s] }
52
+ end
53
+
54
+ private
55
+
56
+ def shell_words(command)
57
+ command.is_a?(Array) ? command : ["sh", "-c", command]
58
+ end
59
+
60
+ def run(argv, **options)
61
+ @project.runner.run(argv_prefix + argv, env: @project.compose_env, **options)
62
+ end
63
+
64
+ def run!(argv, **options)
65
+ @project.runner.run!(argv_prefix + argv, env: @project.compose_env, **options)
66
+ end
67
+ end
68
+ end