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 +7 -0
- data/CHANGELOG.md +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +479 -0
- data/lib/dockside/autostart.rb +33 -0
- data/lib/dockside/commands.rb +60 -0
- data/lib/dockside/compose.rb +68 -0
- data/lib/dockside/dependency.rb +216 -0
- data/lib/dockside/docker.rb +61 -0
- data/lib/dockside/errors.rb +42 -0
- data/lib/dockside/override.rb +51 -0
- data/lib/dockside/project.rb +133 -0
- data/lib/dockside/provisioner.rb +81 -0
- data/lib/dockside/railtie.rb +16 -0
- data/lib/dockside/readiness.rb +120 -0
- data/lib/dockside/registry.rb +55 -0
- data/lib/dockside/runner.rb +55 -0
- data/lib/dockside/settings.rb +38 -0
- data/lib/dockside/version.rb +3 -0
- data/lib/dockside.rb +101 -0
- data/lib/generators/dockside/install_generator.rb +15 -0
- data/lib/generators/dockside/templates/dockside.yml.tt +25 -0
- data/lib/tasks/dockside.rake +38 -0
- metadata +97 -0
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
|
+
[](https://coorasse.semaphoreci.com/projects/dockside)
|
|
4
|
+
[](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
|