mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-21 13:18:00 +00:00
Compare commits
545 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4a4f1ca87 | |||
| f925edd12d | |||
| 12715c8ddc | |||
| 326dadd224 | |||
| a3510c99f1 | |||
| 262d57e387 | |||
| 551092f9c0 | |||
| 3f802f4a13 | |||
| 0b9c8e2860 | |||
| 42fe550c9e | |||
| f5bd137216 | |||
| 348696f3fe | |||
| 6da071985f | |||
| 56e02dd0c7 | |||
| e0a7013836 | |||
| 467dbb75f1 | |||
| 215094903a | |||
| 8a7c0495e0 | |||
| 885afebe07 | |||
| e06372d6f4 | |||
| b5a48eaed3 | |||
| a8059059c6 | |||
| c8c69a1499 | |||
| 0469e47674 | |||
| 5b630d436d | |||
| 9c06420b18 | |||
| 87872006ce | |||
| abfa7a204d | |||
| 5b0eaef602 | |||
| 9694fb85d7 | |||
| bfee2c726e | |||
| ab4fb9bbfa | |||
| fbd5b20c38 | |||
| ff30eb96b6 | |||
| 749a7d0e4f | |||
| d9f769930b | |||
| d750060f0c | |||
| bdbac9f7a1 | |||
| 13201775de | |||
| ccc3969536 | |||
| 89ee524229 | |||
| 9fce6f662a | |||
| 9087ef9a77 | |||
| aa2ab5143b | |||
| 2c147dd721 | |||
| f69c465231 | |||
| 75f4fd978d | |||
| d627ff9697 | |||
| 04c16f347b | |||
| 9c829cb5b4 | |||
| 83acd37161 | |||
| b21f898620 | |||
| f6653c3fa5 | |||
| 18962d0ff3 | |||
| 319988336c | |||
| 67c9ce6dd1 | |||
| 687d27ab57 | |||
| e6affb1b1a | |||
| f409c11916 | |||
| 3885ee00c5 | |||
| 5325703c27 | |||
| 07a887032a | |||
| 1843a71911 | |||
| 9ce577782c | |||
| 422d4afdd5 | |||
| 09a08e0a9f | |||
| 5142e83d93 | |||
| 0418702cfc | |||
| 674e5c8503 | |||
| 2ec141da54 | |||
| bca19a22c5 | |||
| 62b45f0827 | |||
| 92a2b635a3 | |||
| d7979ef2d0 | |||
| c5d8844d80 | |||
| c7cda86e84 | |||
| 4caa604793 | |||
| 7f9ba14b18 | |||
| 72660a1a2f | |||
| d4eab77f0c | |||
| 9708597b0b | |||
| 15bc2240ac | |||
| 631c449183 | |||
| 84a0274885 | |||
| 5ad8840024 | |||
| 8dfc47307d | |||
| aae04def7b | |||
| f51eb0e4b3 | |||
| 8b6b187a8d | |||
| 8b26e4ea3c | |||
| 1d8562b290 | |||
| 9662f9e56a | |||
| 25d71fb01b | |||
| 12ffb522a6 | |||
| 0077678844 | |||
| 53f2c5d6e8 | |||
| 49597f0f52 | |||
| c84d74df8c | |||
| 3c9f9945c9 | |||
| 80ebff0fa4 | |||
| 62c0f433fa | |||
| 44eb1b3892 | |||
| 4cb57a5438 | |||
| 2e5642452b | |||
| 4947e32acb | |||
| 6c87db97a6 | |||
| f127c471a1 | |||
| 280901e5fb | |||
| f14db49346 | |||
| f1cdba2937 | |||
| 0794f0b518 | |||
| e2409ad337 | |||
| ca92bc7798 | |||
| e4f35dd4cf | |||
| 2cebe09924 | |||
| e5f1b6b9a4 | |||
| 79fc90b646 | |||
| fb7c4214ce | |||
| 06a092bdb5 | |||
| 5d25f3232c | |||
| 1a1c89556f | |||
| 05219d6b52 | |||
| 9cc3d7a18b | |||
| e09863fedb | |||
| 2ba1300773 | |||
| 6cb908bb82 | |||
| 5ee3c58d25 | |||
| c3773740d9 | |||
| 0ca133dd76 | |||
| 68934878f1 | |||
| ef1d1aefa7 | |||
| c015d26e96 | |||
| f6760fca88 | |||
| ec61c46bf7 | |||
| 90cb66f08d | |||
| 999a8d7249 | |||
| 13acf72a3e | |||
| aa213b48a4 | |||
| 9b2a5964fc | |||
| 1110bb8e98 | |||
| 168e2f8c49 | |||
| 86ef6422f3 | |||
| 3af60bf375 | |||
| 123c80467b | |||
| edba1af304 | |||
| 875e16c11b | |||
| 703905d7ec | |||
| d52c149075 | |||
| 3f95d1b9bf | |||
| def9598ed9 | |||
| 529ee848da | |||
| 64b817a5c1 | |||
| 02b8d14bdd | |||
| 025c759e44 | |||
| 940d58806c | |||
| a2fb870ce3 | |||
| e737856a7f | |||
| ae1909b482 | |||
| d75282eb14 | |||
| cd9886f0a8 | |||
| a43bae4c0b | |||
| 91ae135896 | |||
| 57b49efc98 | |||
| 3bd73a9633 | |||
| e3acbff2ed | |||
| d6e5c2c276 | |||
| 4dc04d7690 | |||
| 3e12349831 | |||
| 7c50221de5 | |||
| 3d63087f78 | |||
| 1408f08c40 | |||
| 3b23f02229 | |||
| fd5099c9fe | |||
| 68b09cbe3d | |||
| 92cd2f5bad | |||
| 8d1cd63dfa | |||
| 2c4c10fb4a | |||
| 722d4e916a | |||
| 4ff649a4ea | |||
| 641ddaeb03 | |||
| 2ba5c91547 | |||
| 25e7227c81 | |||
| 04cd0a392b | |||
| db9d68c3e4 | |||
| 88cb9f3116 | |||
| 55f9610cde | |||
| 6178dc7f1b | |||
| b8f8f9d07e | |||
| 1643943402 | |||
| e99a99eb6e | |||
| df9712f1c8 | |||
| 28d38620f0 | |||
| 1ce9b3ca9c | |||
| 5c56b597a9 | |||
| f712e10cb2 | |||
| 213365c2d2 | |||
| 7f02bd3b7a | |||
| ceb33818cd | |||
| 18fc707fdf | |||
| c3027fa9ac | |||
| 4249867e5b | |||
| c804a5f827 | |||
| be77ee33bc | |||
| 5928ed5d45 | |||
| 075d30dbce | |||
| 6a6a2ad8a4 | |||
| 279fe144e1 | |||
| cc80bd41c4 | |||
| 06183e6cdc | |||
| 6249cc3373 | |||
| 80f34c6aeb | |||
| a2f526dadc | |||
| 429d2f85cb | |||
| 2ca018b2eb | |||
| 3e6e08ce00 | |||
| 3e491f8698 | |||
| ccffa69766 | |||
| 10190be5d7 | |||
| 65a4e30825 | |||
| e63960caae | |||
| e2ca600fd8 | |||
| 4860df1689 | |||
| ced93bcabd | |||
| 3104fc8d33 | |||
| 2409df9285 | |||
| 2e37be973f | |||
| 6115348dd9 | |||
| 416d098688 | |||
| 6bbe66d2e6 | |||
| 3782e34e67 | |||
| 84790aafd8 | |||
| fea2d5f2fe | |||
| 9f1c9686e0 | |||
| e0a6f27d1b | |||
| 9130ee7513 | |||
| 65c8e9242c | |||
| 1d654f6156 | |||
| 4af24e11a4 | |||
| 583f5868c9 | |||
| d94b186080 | |||
| 5b8cfbe15c | |||
| 0dbc51f4d2 | |||
| 754ed9043d | |||
| ba17f4a06a | |||
| b9149f45bf | |||
| b6c4b325a4 | |||
| 965de6ef50 | |||
| 303490168f | |||
| c7c2399be9 | |||
| 120a520a22 | |||
| 7c03bd1e7a | |||
| 049d28868e | |||
| 8934f13615 | |||
| dcf9dfb129 | |||
| 7c9604e32b | |||
| b302ae329c | |||
| ff6b6f2ce1 | |||
| d49f141fb3 | |||
| 590fc58de7 | |||
| c03561eea8 | |||
| 4179f25286 | |||
| e54e8fa920 | |||
| 2f1c05d997 | |||
| baa4618e57 | |||
| dcc1e3562f | |||
| f6fa353dd8 | |||
| d4fa619ed1 | |||
| 8a851af5e6 | |||
| 8aa9985ad0 | |||
| 2c85b964e3 | |||
| 9159c819c3 | |||
| 484ba4a8c5 | |||
| 97b7b173b9 | |||
| 29f69211c9 | |||
| 553ce165c1 | |||
| 310ddec823 | |||
| 6926ba558f | |||
| 97d1d6f5d2 | |||
| 5f7abd5347 | |||
| 946bbee39a | |||
| bdc0e8f825 | |||
| 1b08ab92d1 | |||
| feda3db1dd | |||
| ce97a71adf | |||
| d00fb40967 | |||
| 0f249c85ea | |||
| a37b42b57c | |||
| dd8c59892c | |||
| a97cf23355 | |||
| 030cc8d5cc | |||
| c22f2617ad | |||
| c860945be2 | |||
| 51d0c9238b | |||
| 22df1249b5 | |||
| db0ccaac9b | |||
| cd6aeaf979 | |||
| 708cd34586 | |||
| 4dcc0f316c | |||
| dae585c6e4 | |||
| b1d994e3b9 | |||
| dde8f74cea | |||
| 9e3d18e606 | |||
| 3e9575e275 | |||
| 07fee44559 | |||
| b6bff0cbb1 | |||
| ead7eb619e | |||
| 29de2432ea | |||
| ffcba1236c | |||
| 0f088d28c5 | |||
| 16917275ee | |||
| 09ab2bfa1d | |||
| b9f0695924 | |||
| 7bade49d4c | |||
| 3a123bc479 | |||
| 5bc344ab73 | |||
| 4bd287e107 | |||
| 6a56b7b391 | |||
| 7dac26ce69 | |||
| 793fd983ef | |||
| 2424480e2c | |||
| f5a8e90d10 | |||
| 2e7df5182c | |||
| ea2486d631 | |||
| 2e72882216 | |||
| d1e1a8e78c | |||
| ad3b091d53 | |||
| 26229d78f2 | |||
| e0c24ccfc3 | |||
| 3b89d13aaf | |||
| 108635582f | |||
| 0c0077ed6f | |||
| 9a604acc23 | |||
| cd9f7f29d1 | |||
| a7068510a5 | |||
| 73c6d9f135 | |||
| 3043c26419 | |||
| e66e0289ab | |||
| 6166380d76 | |||
| e4752c8c1a | |||
| 99c27fa0dd | |||
| d5a57a4b5e | |||
| b14c251862 | |||
| bcd6ca3685 | |||
| 16225c473f | |||
| 46f59dd933 | |||
| e1fa989ec9 | |||
| dd2f0d89bf | |||
| 0b57e4483a | |||
| 7d0b8c726c | |||
| f2dc49292f | |||
| a7ace535c3 | |||
| c99e8de5a4 | |||
| c06aa62bda | |||
| 9ba998312d | |||
| 7d68afb2d2 | |||
| bfdf1839e0 | |||
| 5dc86d7bca | |||
| f35491190a | |||
| 5b27652ac6 | |||
| 394941b6b0 | |||
| 0f134b4bf8 | |||
| 0badda9f15 | |||
| e3f68b22d8 | |||
| 8ca7b0646e | |||
| 10e37198aa | |||
| f7542664e3 | |||
| 950d0312dc | |||
| c8ec36d1b9 | |||
| 17ffb0ac84 | |||
| b2aa877bf0 | |||
| bb241c10e2 | |||
| 3852d05990 | |||
| 63876e7dbd | |||
| 97a2dc96f2 | |||
| 88b6ea993d | |||
| d4f7039793 | |||
| 4399f65fb8 | |||
| 2d85a20c71 | |||
| c01d17d77d | |||
| ed0ba04da6 | |||
| b15cfd3530 | |||
| a438d505ba | |||
| 5eb590e79d | |||
| bdc79ac8b2 | |||
| a97d858b2a | |||
| e592f1b298 | |||
| 513a567483 | |||
| faf103e6ec | |||
| e608296bc6 | |||
| 4ebe2cf348 | |||
| f4b63d9eea | |||
| 422378cb85 | |||
| 594c818d85 | |||
| d86898b014 | |||
| be087c9c82 | |||
| 9cc8b72a38 | |||
| 3425c1b84c | |||
| 12d575a6b1 | |||
| 1c509f4350 | |||
| 48833c7b07 | |||
| f385f1860b | |||
| 01e98bf0dd | |||
| f1dd299227 | |||
| 7df6c4b9ad | |||
| 8f6b6d5784 | |||
| 0fc11699ab | |||
| 9332c00ca5 | |||
| fd9ad1a194 | |||
| 6ae3305040 | |||
| 94053b4225 | |||
| 77ff537697 | |||
| 362f1735e6 | |||
| 7813f2a25e | |||
| 3781a2cc4b | |||
| d47df21a33 | |||
| 0ac672fea6 | |||
| c19fa83a8a | |||
| 844a9ab85e | |||
| a45490243b | |||
| b06f627139 | |||
| cc0e091a6b | |||
| 8742649aa7 | |||
| 0e71e368a8 | |||
| dfc076a123 | |||
| 066873ebd2 | |||
| f6e1055727 | |||
| 6057e6ad70 | |||
| ca39f15fa3 | |||
| 7953d1becb | |||
| f4b41e1a6c | |||
| 4bc3328e80 | |||
| ebe17f57ff | |||
| ee05f97c9a | |||
| 78c02d038f | |||
| bc823acc25 | |||
| c21c5afe00 | |||
| 1ae54707a0 | |||
| ede1e6d475 | |||
| e701dce339 | |||
| a93a87f64a | |||
| 7aba68cd54 | |||
| dfc64d157a | |||
| a41384ad73 | |||
| ed7a4236b3 | |||
| 040c3b50d0 | |||
| 8b3519c5f7 | |||
| ec559c02b8 | |||
| 2e4d4c9f60 | |||
| b8d52ec266 | |||
| b5da816487 | |||
| 3bae188b8d | |||
| 8165e51ecc | |||
| 9a15db21a6 | |||
| 58a1c5720a | |||
| bc172e5e5f | |||
| 6745bdd0b3 | |||
| 5714f0a74e | |||
| ce43f4af1c | |||
| 945033f1cc | |||
| 5d1609f5a2 | |||
| 4714f102d7 | |||
| a675da65e9 | |||
| e39755666b | |||
| 9adba5e2e6 | |||
| 5c1af383eb | |||
| c81662eae4 | |||
| 8ea9ccf3a7 | |||
| 74a2b13687 | |||
| 4e7f2b757e | |||
| 2bba279cf1 | |||
| 56da77a548 | |||
| 494b575213 | |||
| c383d59880 | |||
| 416fdaa3d5 | |||
| 2b0ebf5d32 | |||
| f236e62d9d | |||
| 964e826a9b | |||
| 49673fc45c | |||
| 3342bdb331 | |||
| 1d02a7ffb6 | |||
| 788935175e | |||
| 32663bf431 | |||
| df86cb9a5c | |||
| e3be2e959b | |||
| 67f1e3f5ed | |||
| 23ea9b8968 | |||
| 921c6994b1 | |||
| ea12588524 | |||
| e8ad82f9ba | |||
| 6e2e4cad73 | |||
| 2e0e455fa6 | |||
| d93742fe9a | |||
| 2e3b660dd0 | |||
| 0bd534adcf | |||
| e59dd2c62c | |||
| 25be79208a | |||
| 2a3b91e3b6 | |||
| 221ee504aa | |||
| 64e74cefb7 | |||
| eb4a0271fb | |||
| cfec1c3e1b | |||
| 2b5386f039 | |||
| a0eec14c7d | |||
| 54f9cdb0c3 | |||
| d6fb313220 | |||
| 0aa2470c76 | |||
| 0afed3eded | |||
| 39ff542142 | |||
| edc68f84f3 | |||
| 0089dd05e9 | |||
| cd209f406e | |||
| 1b0fd7ead3 | |||
| a926cd4d88 | |||
| 5d8efc107d | |||
| 1a226f0e28 | |||
| 7c00201222 | |||
| 2db99441c8 | |||
| 6e8bfc8d12 | |||
| 48a892bee5 | |||
| fb005a3da8 | |||
| 92cbb7cc80 | |||
| 599f85d4e4 | |||
| 5756ba9bc4 | |||
| 0900d3b7a6 | |||
| 24dd50490a | |||
| d639f61ec1 | |||
| a3b1a9f01a | |||
| 507cef8bce | |||
| 166eba3e28 | |||
| 080243f040 | |||
| e9011122fb | |||
| 941e3e2ef0 | |||
| 73e79a3310 | |||
| 34cf976866 | |||
| e832b01349 | |||
| 26c8eae6fe | |||
| d40efc4648 | |||
| 5705a49308 | |||
| 65185a565b | |||
| 1bb8f60d5a | |||
| 1d01189f04 | |||
| fc3a8e409d | |||
| 8eeff01939 | |||
| 4388bef996 | |||
| 64450ae3f8 |
+1
-3
@@ -5,10 +5,7 @@ docker/docker
|
||||
a.out
|
||||
*.orig
|
||||
build_src
|
||||
command-line-arguments.test
|
||||
.flymake*
|
||||
docker.test
|
||||
auth/auth.test
|
||||
.idea
|
||||
.DS_Store
|
||||
docs/_build
|
||||
@@ -16,3 +13,4 @@ docs/_static
|
||||
docs/_templates
|
||||
.gopath/
|
||||
.dotcloud
|
||||
*.test
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generate AUTHORS: git log --all --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
|
||||
# Generate AUTHORS: git log --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
@@ -23,3 +23,6 @@ Thatcher Peskens <thatcher@dotcloud.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
# For a list of active project maintainers, see the MAINTAINERS file.
|
||||
#
|
||||
Al Tobey <al@ooyala.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Alexey Shamrin <shamrin@gmail.com>
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andreas Tiefenthaler <at@an-ti.eu>
|
||||
Andrew Munsell <andrew@wizardapps.net>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andy Rothfusz <github@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Anthony Bishopric <git@anthonybishopric.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
Barry Allard <barry.allard@gmail.com>
|
||||
Brandon Liu <bdon@bdon.org>
|
||||
@@ -19,21 +22,29 @@ Caleb Spare <cespare@gmail.com>
|
||||
Calen Pennington <cale@edx.org>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Christopher Currie <codemonkey+github@gmail.com>
|
||||
Colin Rice <colin@daedrum.net>
|
||||
Daniel Gasienica <daniel@gasienica.ch>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Daniel YC Lin <dlin.tw@gmail.com>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Dominik Honnef <dominik@honnef.co>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||
Elias Probst <mail@eliasprobst.eu>
|
||||
Eric Hanchrow <ehanchrow@ine.com>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
Eric Myhre <hash@exultant.us>
|
||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Fabrizio Regini <freegenie@gmail.com>
|
||||
Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||
Flavio Castelli <fcastelli@suse.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
Gabriel Monroy <gabriel@opdemand.com>
|
||||
Gareth Rushgrove <gareth@morethanseven.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
@@ -41,6 +52,7 @@ Hunter Blanks <hunter@twilio.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeremy Grosser <jeremy@synack.me>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
John Costa <john.costa@gmail.com>
|
||||
Jon Wedaman <jweede@gmail.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
@@ -48,40 +60,57 @@ Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Karan Lyons <karan@karanlyons.com>
|
||||
Keli Hu <dev@keli.hu>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Kevin Clark <kevin.clark@gmail.com>
|
||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||
kim0 <email.ahmedkamal@googlemail.com>
|
||||
Kimbro Staken <kstaken@kstaken.com>
|
||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Marco Hennings <marco.hennings@freiheit.com>
|
||||
Marcus Farkas <toothlessgear@finitebox.com>
|
||||
Mark McGranaghan <mmcgrana@gmail.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
meejah <meejah@meejah.ca>
|
||||
Michael Crosby <crosby.michael@gmail.com>
|
||||
Mike Gaffney <mike@uberu.com>
|
||||
Mikhail Sobolev <mss@mawhrin.net>
|
||||
Nan Monnand Deng <monnand@gmail.com>
|
||||
Nate Jones <nate@endot.org>
|
||||
Nelson Chen <crazysim@gmail.com>
|
||||
Niall O'Higgins <niallo@unworkable.org>
|
||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||
Nick Stinemates <nick@stinemates.org>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
odk- <github@odkurzacz.org>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
Piotr Bogdan <ppbogdan@gmail.com>
|
||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||
Rhys Hiltner <rhys@twitch.tv>
|
||||
Robert Obryk <robryk@gmail.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Ryan Fowler <rwfowler@gmail.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Silas Sewell <silas@sewell.org>
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Stefan Praszalowicz <stefan@greplin.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
Thomas Bikeev <thomas.bikeev@mac.com>
|
||||
Thomas Hansen <thomas.hansen@gmail.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||
Tobias Schmidt <ts@soundcloud.com>
|
||||
Tobias Schwab <tobias.schwab@dynport.de>
|
||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||
unclejack <unclejacksons@gmail.com>
|
||||
Victor Vieux <victor.vieux@dotcloud.com>
|
||||
Vivek Agarwal <me@vivek.im>
|
||||
|
||||
+125
@@ -1,5 +1,130 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.0 (2013-08-22)
|
||||
- Runtime: Load authConfig only when needed and fix useless WARNING
|
||||
+ Runtime: Add lxc-conf flag to allow custom lxc options
|
||||
- Runtime: Fix race conditions in parallel pull
|
||||
- Runtime: Improve CMD, ENTRYPOINT, and attach docs.
|
||||
* Documentation: Small fix to docs regarding adding docker groups
|
||||
* Documentation: Add MongoDB image example
|
||||
+ Builder: Add USER instruction do Dockerfile
|
||||
* Documentation: updated default -H docs
|
||||
* Remote API: Sort Images by most recent creation date.
|
||||
+ Builder: Add workdir support for the Buildfile
|
||||
+ Runtime: Add an option to set the working directory
|
||||
- Runtime: Show tag used when image is missing
|
||||
* Documentation: Update readme with dependencies for building
|
||||
* Documentation: Add instructions for creating and using the docker group
|
||||
* Remote API: Reworking opaque requests in registry module
|
||||
- Runtime: Fix Graph ByParent() to generate list of child images per parent image.
|
||||
* Runtime: Add Image name to LogEvent tests
|
||||
* Documentation: Add sudo to examples and installation to documentation
|
||||
+ Hack: Bash Completion: Limit commands to containers of a relevant state
|
||||
* Remote API: Add image name in /events
|
||||
* Runtime: Apply volumes-from before creating volumes
|
||||
- Runtime: Make docker run handle SIGINT/SIGTERM
|
||||
- Runtime: Prevent crash when .dockercfg not readable
|
||||
* Hack: Add docker dependencies coverage testing into docker-ci
|
||||
+ Runtime: Add -privileged flag and relevant tests, docs, and examples
|
||||
+ Packaging: Docker-brew 0.5.2 support and memory footprint reduction
|
||||
- Runtime: Install script should be fetched over https, not http.
|
||||
* Packaging: Add new docker dependencies into docker-ci
|
||||
* Runtime: Use Go 1.1.2 for dockerbuilder
|
||||
* Registry: Improve auth push
|
||||
* Runtime: API, issue 1471: Use groups for socket permissions
|
||||
* Documentation: PostgreSQL service example in documentation
|
||||
* Contrib: bash completion script
|
||||
* Tests: Improve TestKillDifferentUser to prevent timeout on buildbot
|
||||
* Documentation: Fix typo in docs for docker run -dns
|
||||
* Documentation: Adding a reference to ps -a
|
||||
- Runtime: Correctly detect IPv4 forwarding
|
||||
- Packaging: Revert "docker.upstart: avoid spawning a `sh` process"
|
||||
* Runtime: Use ranged for loop on channels
|
||||
- Runtime: Fix typo: fmt.Sprint -> fmt.Sprintf
|
||||
- Tests: Fix typo in TestBindMounts (runContainer called without image)
|
||||
* Runtime: add websocket support to /container/<name>/attach/ws
|
||||
* Runtime: Mount /dev/shm as a tmpfs
|
||||
- Builder: Only count known instructions as build steps
|
||||
- Builder: Fix docker build and docker events output
|
||||
- Runtime: switch from http to https for get.docker.io
|
||||
* Tests: Improve TestGetContainersTop so it does not rely on sleep
|
||||
+ Packaging: Docker-brew and Docker standard library
|
||||
* Testing: Add some tests in server and utils
|
||||
+ Packaging: Release docker with docker
|
||||
- Builder: Make sure ENV instruction within build perform a commit each time
|
||||
* Packaging: Fix the upstart script generated by get.docker.io
|
||||
- Runtime: fix small \n error un docker build
|
||||
* Runtime: Let userland proxy handle container-bound traffic
|
||||
* Runtime: Updated the Docker CLI to specify a value for the "Host" header.
|
||||
* Runtime: Add warning when net.ipv4.ip_forwarding = 0
|
||||
* Registry: Registry unit tests + mock registry
|
||||
* Runtime: fixed #910. print user name to docker info output
|
||||
- Builder: Forbid certain paths within docker build ADD
|
||||
- Runtime: change network range to avoid conflict with EC2 DNS
|
||||
* Tests: Relax the lo interface test to allow iface index != 1
|
||||
* Documentation: Suggest installing linux-headers by default.
|
||||
* Documentation: Change the twitter handle
|
||||
* Client: Add docker cp command and copy api endpoint to copy container files/folders to the host
|
||||
* Remote API: Use mime pkg to parse Content-Type
|
||||
- Runtime: Reduce connect and read timeout when pinging the registry
|
||||
* Documentation: Update amazon.rst to explain that Vagrant is not necessary for running Docker on ec2
|
||||
* Packaging: Enabled the docs to generate manpages.
|
||||
* Runtime: Parallel pull
|
||||
- Runtime: Handle ip route showing mask-less IP addresses
|
||||
* Documentation: Clarify Amazon EC2 installation
|
||||
* Documentation: 'Base' image is deprecated and should no longer be referenced in the docs.
|
||||
* Runtime: Fix to "Inject dockerinit at /.dockerinit"
|
||||
* Runtime: Allow ENTRYPOINT without CMD
|
||||
- Runtime: Always consider localhost as a domain name when parsing the FQN repos name
|
||||
* Remote API: 650 http utils and user agent field
|
||||
* Documentation: fix a typo in the ubuntu installation guide
|
||||
- Builder: Repository name (and optionally a tag) in build usage
|
||||
* Documentation: Move note about officially supported kernel
|
||||
* Packaging: Revert "Bind daemon to 0.0.0.0 in Vagrant.
|
||||
* Builder: Add no cache for docker build
|
||||
* Runtime: Add hostname to environment
|
||||
* Runtime: Add last stable version in `docker version`
|
||||
- Builder: Make sure ADD will create everything in 0755
|
||||
* Documentation: Add ufw doc
|
||||
* Tests: Add registry functional test to docker-ci
|
||||
- Documentation: Solved the logo being squished in Safari
|
||||
- Runtime: Use utils.ParseRepositoryTag instead of strings.Split(name, ":") in server.ImageDelete
|
||||
* Runtime: Refactor checksum
|
||||
- Runtime: Improve connect message with socket error
|
||||
* Documentation: Added information about Docker's high level tools over LXC.
|
||||
* Don't read from stdout when only attached to stdin
|
||||
|
||||
## 0.5.3 (2013-08-13)
|
||||
* Runtime: Use docker group for socket permissions
|
||||
- Runtime: Spawn shell within upstart script
|
||||
- Builder: Make sure ENV instruction within build perform a commit each time
|
||||
- Runtime: Handle ip route showing mask-less IP addresses
|
||||
- Runtime: Add hostname to environment
|
||||
|
||||
## 0.5.2 (2013-08-08)
|
||||
* Builder: Forbid certain paths within docker build ADD
|
||||
- Runtime: Change network range to avoid conflict with EC2 DNS
|
||||
* API: Change daemon to listen on unix socket by default
|
||||
|
||||
## 0.5.1 (2013-07-30)
|
||||
+ API: Docker client now sets useragent (RFC 2616)
|
||||
+ Runtime: Add `ps` args to `docker top`
|
||||
+ Runtime: Add support for container ID files (pidfile like)
|
||||
+ Runtime: Add container=lxc in default env
|
||||
+ Runtime: Support networkless containers with `docker run -n` and `docker -d -b=none`
|
||||
+ API: Add /events endpoint
|
||||
+ Builder: ADD command now understands URLs
|
||||
+ Builder: CmdAdd and CmdEnv now respect Dockerfile-set ENV variables
|
||||
* Hack: Simplify unit tests with helpers
|
||||
* Hack: Improve docker.upstart event
|
||||
* Hack: Add coverage testing into docker-ci
|
||||
* Runtime: Stdout/stderr logs are now stored in the same file as JSON
|
||||
* Runtime: Allocate a /16 IP range by default, with fallback to /24. Try 12 ranges instead of 3.
|
||||
* Runtime: Change .dockercfg format to json and support multiple auth remote
|
||||
- Runtime: Do not override volumes from config
|
||||
- Runtime: Fix issue with EXPOSE override
|
||||
- Builder: Create directories with 755 instead of 700 within ADD instruction
|
||||
|
||||
## 0.5.0 (2013-07-17)
|
||||
+ Runtime: List all processes running inside a container with 'docker top'
|
||||
+ Runtime: Host directories can be mounted as volumes with 'docker run -v'
|
||||
|
||||
+20
-11
@@ -3,28 +3,37 @@ docker-version 0.4.2
|
||||
from ubuntu:12.04
|
||||
maintainer Solomon Hykes <solomon@dotcloud.com>
|
||||
# Build dependencies
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
run apt-get install -y -q curl
|
||||
run apt-get install -y -q git
|
||||
run apt-get install -y -q mercurial
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz | tar -v -C /usr/local -xz
|
||||
run curl -s https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz | tar -v -C /usr/local -xz
|
||||
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
env GOPATH /go
|
||||
env CGO_ENABLED 0
|
||||
run cd /tmp && echo 'package main' > t.go && go test -a -i -v
|
||||
# Ubuntu stuff
|
||||
run apt-get install -y -q ruby1.9.3 rubygems
|
||||
run gem install fpm
|
||||
run apt-get install -y -q reprepro dpkg-sig
|
||||
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
|
||||
run apt-get install -y -q python-pip
|
||||
run pip install s3cmd
|
||||
run pip install python-magic
|
||||
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
|
||||
# Download dependencies
|
||||
run PKG=github.com/kr/pty REV=27435c699; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
|
||||
run PKG=github.com/gorilla/context/ REV=708054d61e5; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
|
||||
run PKG=github.com/gorilla/mux/ REV=9b36453141c; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
|
||||
# Run dependencies
|
||||
run apt-get install -y iptables
|
||||
# lxc requires updating ubuntu sources
|
||||
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
|
||||
run apt-get update
|
||||
run apt-get install -y lxc
|
||||
run apt-get install -y aufs-tools
|
||||
run PKG=github.com/dotcloud/tar/ REV=d06045a6d9; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
|
||||
# Docker requires code.google.com/p/go.net/websocket
|
||||
run apt-get install -y -q mercurial
|
||||
run PKG=code.google.com/p/go.net/ REV=84a4013f96e0; hg clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && hg checkout $REV
|
||||
# Upload docker source
|
||||
add . /go/src/github.com/dotcloud/docker
|
||||
run ln -s /go/src/github.com/dotcloud/docker /src
|
||||
# Build the binary
|
||||
run cd /go/src/github.com/dotcloud/docker/docker && go install -ldflags "-X main.GITCOMMIT '??' -d -w"
|
||||
env PATH /usr/local/go/bin:/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
cmd ["docker"]
|
||||
run cd /go/src/github.com/dotcloud/docker && hack/release/make.sh
|
||||
cmd cd /go/src/github.com/dotcloud/docker && hack/release/release.sh
|
||||
|
||||
+6
-5
@@ -1,5 +1,6 @@
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Guillaume Charmes <guillaume@dotcloud.com>
|
||||
Victor Vieux <victor@dotcloud.com>
|
||||
api.go: Victor Vieux <victor@dotcloud.com>
|
||||
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
Solomon Hykes <solomon@dotcloud.com> (@shykes)
|
||||
Guillaume Charmes <guillaume@dotcloud.com> (@creack)
|
||||
Victor Vieux <victor@dotcloud.com> (@vieux)
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
api.go: Victor Vieux <victor@dotcloud.com> (@vieux)
|
||||
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com> (@mzdaniel)
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
DOCKER_PACKAGE := github.com/dotcloud/docker
|
||||
RELEASE_VERSION := $(shell git tag | grep -E "v[0-9\.]+$$" | sort -nr | head -n 1)
|
||||
SRCRELEASE := docker-$(RELEASE_VERSION)
|
||||
BINRELEASE := docker-$(RELEASE_VERSION).tgz
|
||||
BUILD_SRC := build_src
|
||||
BUILD_PATH := ${BUILD_SRC}/src/${DOCKER_PACKAGE}
|
||||
|
||||
GIT_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
BUILD_DIR := $(CURDIR)/.gopath
|
||||
|
||||
GOPATH ?= $(BUILD_DIR)
|
||||
export GOPATH
|
||||
|
||||
GO_OPTIONS ?=
|
||||
ifeq ($(VERBOSE), 1)
|
||||
GO_OPTIONS += -v
|
||||
endif
|
||||
|
||||
GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
|
||||
|
||||
BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w"
|
||||
|
||||
SRC_DIR := $(GOPATH)/src
|
||||
|
||||
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
|
||||
DOCKER_MAIN := $(DOCKER_DIR)/docker
|
||||
|
||||
DOCKER_BIN_RELATIVE := bin/docker
|
||||
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
|
||||
|
||||
.PHONY: all clean test hack release srcrelease $(BINRELEASE) $(SRCRELEASE) $(DOCKER_BIN) $(DOCKER_DIR)
|
||||
|
||||
all: $(DOCKER_BIN)
|
||||
|
||||
$(DOCKER_BIN): $(DOCKER_DIR)
|
||||
@mkdir -p $(dir $@)
|
||||
@(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@echo $(DOCKER_BIN_RELATIVE) is created.
|
||||
|
||||
$(DOCKER_DIR):
|
||||
@mkdir -p $(dir $@)
|
||||
@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
|
||||
@(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS))
|
||||
|
||||
whichrelease:
|
||||
echo $(RELEASE_VERSION)
|
||||
|
||||
release: $(BINRELEASE)
|
||||
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
|
||||
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
|
||||
s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker
|
||||
|
||||
srcrelease: $(SRCRELEASE)
|
||||
deps: $(DOCKER_DIR)
|
||||
|
||||
# A clean checkout of $RELEASE_VERSION, with vendored dependencies
|
||||
$(SRCRELEASE):
|
||||
rm -fr $(SRCRELEASE)
|
||||
git clone $(GIT_ROOT) $(SRCRELEASE)
|
||||
cd $(SRCRELEASE); git checkout -q $(RELEASE_VERSION)
|
||||
|
||||
# A binary release ready to be uploaded to a mirror
|
||||
$(BINRELEASE): $(SRCRELEASE)
|
||||
rm -f $(BINRELEASE)
|
||||
cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
|
||||
cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest
|
||||
|
||||
clean:
|
||||
@rm -rf $(dir $(DOCKER_BIN))
|
||||
ifeq ($(GOPATH), $(BUILD_DIR))
|
||||
@rm -rf $(BUILD_DIR)
|
||||
else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR)))
|
||||
@rm -f $(DOCKER_DIR)
|
||||
endif
|
||||
|
||||
test:
|
||||
# Copy docker source and dependencies for testing
|
||||
rm -rf ${BUILD_SRC}; mkdir -p ${BUILD_PATH}
|
||||
tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
|
||||
GOPATH=${CURDIR}/${BUILD_SRC} go get -d
|
||||
# Do the test
|
||||
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS}
|
||||
|
||||
testall: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
hack:
|
||||
cd $(CURDIR)/hack && vagrant up
|
||||
|
||||
ssh-dev:
|
||||
cd $(CURDIR)/hack && vagrant ssh
|
||||
@@ -1,80 +1,129 @@
|
||||
Docker: the Linux container engine
|
||||
==================================
|
||||
|
||||
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers.
|
||||
Docker is an open-source engine which automates the deployment of
|
||||
applications as highly portable, self-sufficient containers.
|
||||
|
||||
Docker containers are both *hardware-agnostic* and *platform-agnostic*. This means that they can run anywhere, from your
|
||||
laptop to the largest EC2 compute instance and everything in between - and they don't require that you use a particular
|
||||
language, framework or packaging system. That makes them great building blocks for deploying and scaling web apps, databases
|
||||
and backend services without depending on a particular stack or provider.
|
||||
Docker containers are both *hardware-agnostic* and
|
||||
*platform-agnostic*. This means that they can run anywhere, from your
|
||||
laptop to the largest EC2 compute instance and everything in between -
|
||||
and they don't require that you use a particular language, framework
|
||||
or packaging system. That makes them great building blocks for
|
||||
deploying and scaling web apps, databases and backend services without
|
||||
depending on a particular stack or provider.
|
||||
|
||||
Docker is an open-source implementation of the deployment engine which powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service.
|
||||
It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
|
||||
of applications and databases.
|
||||
Docker is an open-source implementation of the deployment engine which
|
||||
powers [dotCloud](http://dotcloud.com), a popular
|
||||
Platform-as-a-Service. It benefits directly from the experience
|
||||
accumulated over several years of large-scale operation and support of
|
||||
hundreds of thousands of applications and databases.
|
||||
|
||||

|
||||

|
||||
|
||||
## Better than VMs
|
||||
|
||||
A common method for distributing applications and sandbox their execution is to use virtual machines, or VMs. Typical VM formats
|
||||
are VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory these formats should allow every developer to
|
||||
automatically package their application into a "machine" for easy distribution and deployment. In practice, that almost never
|
||||
happens, for a few reasons:
|
||||
A common method for distributing applications and sandbox their
|
||||
execution is to use virtual machines, or VMs. Typical VM formats are
|
||||
VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In
|
||||
theory these formats should allow every developer to automatically
|
||||
package their application into a "machine" for easy distribution and
|
||||
deployment. In practice, that almost never happens, for a few reasons:
|
||||
|
||||
* *Size*: VMs are very large which makes them impractical to store and transfer.
|
||||
* *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and
|
||||
large-scale deployment of cpu and memory-intensive applications on large numbers of machines.
|
||||
* *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead.
|
||||
* *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most:
|
||||
building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery.
|
||||
* *Size*: VMs are very large which makes them impractical to store
|
||||
and transfer.
|
||||
* *Performance*: running VMs consumes significant CPU and memory,
|
||||
which makes them impractical in many scenarios, for example local
|
||||
development of multi-tier applications, and large-scale deployment
|
||||
of cpu and memory-intensive applications on large numbers of
|
||||
machines.
|
||||
* *Portability*: competing VM environments don't play well with each
|
||||
other. Although conversion tools do exist, they are limited and
|
||||
add even more overhead.
|
||||
* *Hardware-centric*: VMs were designed with machine operators in
|
||||
mind, not software developers. As a result, they offer very
|
||||
limited tooling for what developers need most: building, testing
|
||||
and running their software. For example, VMs offer no facilities
|
||||
for application versioning, monitoring, configuration, logging or
|
||||
service discovery.
|
||||
|
||||
By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization,
|
||||
containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary
|
||||
for containerization, including Linux with [openvz](http://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net),
|
||||
Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html).
|
||||
By contrast, Docker relies on a different sandboxing method known as
|
||||
*containerization*. Unlike traditional virtualization,
|
||||
containerization takes place at the kernel level. Most modern
|
||||
operating system kernels now support the primitives necessary for
|
||||
containerization, including Linux with [openvz](http://openvz.org),
|
||||
[vserver](http://linux-vserver.org) and more recently
|
||||
[lxc](http://lxc.sourceforge.net), Solaris with
|
||||
[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc)
|
||||
and FreeBSD with
|
||||
[Jails](http://www.freebsd.org/doc/handbook/jails.html).
|
||||
|
||||
Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves
|
||||
all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead,
|
||||
they are completely portable and are designed from the ground up with an application-centric design.
|
||||
Docker builds on top of these low-level primitives to offer developers
|
||||
a portable format and runtime environment that solves all 4
|
||||
problems. Docker containers are small (and their transfer can be
|
||||
optimized with layers), they have basically zero memory and cpu
|
||||
overhead, they are completely portable and are designed from the
|
||||
ground up with an application-centric design.
|
||||
|
||||
The best part: because docker operates at the OS level, it can still be run inside a VM!
|
||||
The best part: because ``docker`` operates at the OS level, it can
|
||||
still be run inside a VM!
|
||||
|
||||
## Plays well with others
|
||||
|
||||
Docker does not require that you buy into a particular programming language, framework, packaging system or configuration language.
|
||||
Docker does not require that you buy into a particular programming
|
||||
language, framework, packaging system or configuration language.
|
||||
|
||||
Is your application a unix process? Does it use files, tcp connections, environment variables, standard unix streams and command-line
|
||||
arguments as inputs and outputs? Then docker can run it.
|
||||
Is your application a Unix process? Does it use files, tcp
|
||||
connections, environment variables, standard Unix streams and
|
||||
command-line arguments as inputs and outputs? Then ``docker`` can run
|
||||
it.
|
||||
|
||||
Can your application's build be expressed as a sequence of such commands? Then docker can build it.
|
||||
Can your application's build be expressed as a sequence of such
|
||||
commands? Then ``docker`` can build it.
|
||||
|
||||
|
||||
## Escape dependency hell
|
||||
|
||||
A common problem for developers is the difficulty of managing all their application's dependencies in a simple and automated way.
|
||||
A common problem for developers is the difficulty of managing all
|
||||
their application's dependencies in a simple and automated way.
|
||||
|
||||
This is usually difficult for several reasons:
|
||||
|
||||
* *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules,
|
||||
internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work
|
||||
well with each other, requiring awkward custom integrations.
|
||||
* *Cross-platform dependencies*. Modern applications often depend on
|
||||
a combination of system libraries and binaries, language-specific
|
||||
packages, framework-specific modules, internal components
|
||||
developed for another project, etc. These dependencies live in
|
||||
different "worlds" and require different tools - these tools
|
||||
typically don't work well with each other, requiring awkward
|
||||
custom integrations.
|
||||
|
||||
* Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease -
|
||||
but they all handle them in different and incompatible ways, which again forces the developer to do extra work.
|
||||
* Conflicting dependencies. Different applications may depend on
|
||||
different versions of the same dependency. Packaging tools handle
|
||||
these situations with various degrees of ease - but they all
|
||||
handle them in different and incompatible ways, which again forces
|
||||
the developer to do extra work.
|
||||
|
||||
* Custom dependencies. A developer may need to prepare a custom version of their application's dependency. Some packaging systems can handle custom versions of a dependency,
|
||||
others can't - and all of them handle it differently.
|
||||
* Custom dependencies. A developer may need to prepare a custom
|
||||
version of their application's dependency. Some packaging systems
|
||||
can handle custom versions of a dependency, others can't - and all
|
||||
of them handle it differently.
|
||||
|
||||
|
||||
Docker solves dependency hell by giving the developer a simple way to express *all* their application's dependencies in one place,
|
||||
and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
|
||||
*replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers.
|
||||
Docker solves dependency hell by giving the developer a simple way to
|
||||
express *all* their application's dependencies in one place, and
|
||||
streamline the process of assembling them. If this makes you think of
|
||||
[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
|
||||
*replace* your favorite packaging systems. It simply orchestrates
|
||||
their use in a simple and repeatable way. How does it do that? With
|
||||
layers.
|
||||
|
||||
Docker defines a build as running a sequence of unix commands, one after the other, in the same container. Build commands modify the contents of the container
|
||||
(usually by installing new files on the filesystem), the next command modifies it some more, etc. Since each build command inherits the result of the previous
|
||||
commands, the *order* in which the commands are executed expresses *dependencies*.
|
||||
Docker defines a build as running a sequence of Unix commands, one
|
||||
after the other, in the same container. Build commands modify the
|
||||
contents of the container (usually by installing new files on the
|
||||
filesystem), the next command modifies it some more, etc. Since each
|
||||
build command inherits the result of the previous commands, the
|
||||
*order* in which the commands are executed expresses *dependencies*.
|
||||
|
||||
Here's a typical docker build process:
|
||||
Here's a typical Docker build process:
|
||||
|
||||
```bash
|
||||
from ubuntu:12.10
|
||||
@@ -87,7 +136,8 @@ run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xz
|
||||
run cd helloflask-master && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a unix command in a container.
|
||||
Note that Docker doesn't care *how* dependencies are built - as long
|
||||
as they can be built by running a Unix command in a container.
|
||||
|
||||
|
||||
Install instructions
|
||||
@@ -97,14 +147,15 @@ Quick install on Ubuntu 12.04 and 12.10
|
||||
---------------------------------------
|
||||
|
||||
```bash
|
||||
curl get.docker.io | sudo sh -x
|
||||
curl https://get.docker.io | sudo sh -x
|
||||
```
|
||||
|
||||
Binary installs
|
||||
----------------
|
||||
|
||||
Docker supports the following binary installation methods.
|
||||
Note that some methods are community contributions and not yet officially supported.
|
||||
Docker supports the following binary installation methods. Note that
|
||||
some methods are community contributions and not yet officially
|
||||
supported.
|
||||
|
||||
* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
|
||||
* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
|
||||
@@ -115,7 +166,11 @@ Note that some methods are community contributions and not yet officially suppor
|
||||
Installing from source
|
||||
----------------------
|
||||
|
||||
1. Make sure you have a [Go language](http://golang.org/doc/install) compiler and [git](http://git-scm.com) installed.
|
||||
1. Install Dependencies
|
||||
* [Go language 1.1.x](http://golang.org/doc/install)
|
||||
* [git](http://git-scm.com)
|
||||
* [lxc](http://lxc.sourceforge.net)
|
||||
* [aufs-tools](http://aufs.sourceforge.net)
|
||||
|
||||
2. Checkout the source code
|
||||
|
||||
@@ -123,7 +178,7 @@ Installing from source
|
||||
git clone http://github.com/dotcloud/docker
|
||||
```
|
||||
|
||||
3. Build the docker binary
|
||||
3. Build the ``docker`` binary
|
||||
|
||||
```bash
|
||||
cd docker
|
||||
@@ -134,17 +189,20 @@ Installing from source
|
||||
Usage examples
|
||||
==============
|
||||
|
||||
First run the docker daemon
|
||||
---------------------------
|
||||
First run the ``docker`` daemon
|
||||
-------------------------------
|
||||
|
||||
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
|
||||
All the examples assume your machine is running the ``docker``
|
||||
daemon. To run the ``docker`` daemon in the background, simply type:
|
||||
|
||||
```bash
|
||||
# On a production system you want this running in an init script
|
||||
sudo docker -d &
|
||||
```
|
||||
|
||||
Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client can run from any account.
|
||||
Now you can run ``docker`` in client mode: all commands will be
|
||||
forwarded to the ``docker`` daemon, so the client can run from any
|
||||
account.
|
||||
|
||||
```bash
|
||||
# Now you can run docker commands from any account.
|
||||
@@ -152,7 +210,7 @@ docker help
|
||||
```
|
||||
|
||||
|
||||
Throwaway shell in a base ubuntu image
|
||||
Throwaway shell in a base Ubuntu image
|
||||
--------------------------------------
|
||||
|
||||
```bash
|
||||
@@ -202,7 +260,8 @@ docker commit -m "Installed curl" $CONTAINER $USER/betterbase
|
||||
docker push $USER/betterbase
|
||||
```
|
||||
|
||||
A list of publicly available images is [available here](https://github.com/dotcloud/docker/wiki/Public-docker-images).
|
||||
A list of publicly available images is [available
|
||||
here](https://github.com/dotcloud/docker/wiki/Public-docker-images).
|
||||
|
||||
Expose a service on a TCP port
|
||||
------------------------------
|
||||
@@ -229,32 +288,40 @@ Under the hood
|
||||
|
||||
Under the hood, Docker is built on the following components:
|
||||
|
||||
|
||||
* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel;
|
||||
|
||||
* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
|
||||
|
||||
* The
|
||||
[cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c)
|
||||
and
|
||||
[namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part)
|
||||
capabilities of the Linux kernel;
|
||||
* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union
|
||||
filesystem with copy-on-write capabilities;
|
||||
* The [Go](http://golang.org) programming language;
|
||||
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to
|
||||
simplify the creation of Linux containers.
|
||||
|
||||
|
||||
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docs.docker.io/en/latest/contributing/contributing/
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
started on the website:
|
||||
http://docs.docker.io/en/latest/contributing/contributing/
|
||||
|
||||
They are probably not perfect, please let us know if anything feels wrong or incomplete.
|
||||
They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
|
||||
|
||||
Note
|
||||
----
|
||||
|
||||
We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources.
|
||||
Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/tree/master/docs/README.md
|
||||
We also keep the documentation in this repository. The website
|
||||
documentation is generated using Sphinx using these sources. Please
|
||||
find it under docs/sources/ and read more about it
|
||||
https://github.com/dotcloud/docker/tree/master/docs/README.md
|
||||
|
||||
Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
|
||||
Please feel free to fix / update the documentation and send us pull
|
||||
requests. More tutorials are also welcome.
|
||||
|
||||
|
||||
Setting up a dev environment
|
||||
@@ -289,93 +356,104 @@ Run the `go install` command (above) to recompile docker.
|
||||
What is a Standard Container?
|
||||
=============================
|
||||
|
||||
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
|
||||
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependencies, regardless of the underlying machine and the contents of the container.
|
||||
Docker defines a unit of software delivery called a Standard
|
||||
Container. The goal of a Standard Container is to encapsulate a
|
||||
software component and all its dependencies in a format that is
|
||||
self-describing and portable, so that any compliant runtime can run it
|
||||
without extra dependencies, regardless of the underlying machine and
|
||||
the contents of the container.
|
||||
|
||||
The spec for Standard Containers is currently a work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
|
||||
The spec for Standard Containers is currently a work in progress, but
|
||||
it is very straightforward. It mostly defines 1) an image format, 2) a
|
||||
set of standard operations, and 3) an execution environment.
|
||||
|
||||
A great analogy for this is the shipping container. Just like how Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
|
||||
A great analogy for this is the shipping container. Just like how
|
||||
Standard Containers are a fundamental unit of software delivery,
|
||||
shipping containers are a fundamental unit of physical delivery.
|
||||
|
||||
### 1. STANDARD OPERATIONS
|
||||
|
||||
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
|
||||
Just like shipping containers, Standard Containers define a set of
|
||||
STANDARD OPERATIONS. Shipping containers can be lifted, stacked,
|
||||
locked, loaded, unloaded and labelled. Similarly, Standard Containers
|
||||
can be started, stopped, copied, snapshotted, downloaded, uploaded and
|
||||
tagged.
|
||||
|
||||
|
||||
### 2. CONTENT-AGNOSTIC
|
||||
|
||||
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
|
||||
Just like shipping containers, Standard Containers are
|
||||
CONTENT-AGNOSTIC: all standard operations have the same effect
|
||||
regardless of the contents. A shipping container will be stacked in
|
||||
exactly the same way whether it contains Vietnamese powder coffee or
|
||||
spare Maserati parts. Similarly, Standard Containers are started or
|
||||
uploaded in the same way whether they contain a postgres database, a
|
||||
php application with its dependencies and application server, or Java
|
||||
build artifacts.
|
||||
|
||||
|
||||
### 3. INFRASTRUCTURE-AGNOSTIC
|
||||
|
||||
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
|
||||
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be
|
||||
transported to thousands of facilities around the world, and
|
||||
manipulated by a wide variety of equipment. A shipping container can
|
||||
be packed in a factory in Ukraine, transported by truck to the nearest
|
||||
routing center, stacked onto a train, loaded into a German boat by an
|
||||
Australian-built crane, stored in a warehouse at a US facility,
|
||||
etc. Similarly, a standard container can be bundled on my laptop,
|
||||
uploaded to S3, downloaded, run and snapshotted by a build server at
|
||||
Equinix in Virginia, uploaded to 10 staging servers in a home-made
|
||||
Openstack cluster, then sent to 30 production instances across 3 EC2
|
||||
regions.
|
||||
|
||||
|
||||
### 4. DESIGNED FOR AUTOMATION
|
||||
|
||||
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
|
||||
Because they offer the same standard operations regardless of content
|
||||
and infrastructure, Standard Containers, just like their physical
|
||||
counterparts, are extremely well-suited for automation. In fact, you
|
||||
could say automation is their secret weapon.
|
||||
|
||||
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
|
||||
Many things that once required time-consuming and error-prone human
|
||||
effort can now be programmed. Before shipping containers, a bag of
|
||||
powder coffee was hauled, dragged, dropped, rolled and stacked by 10
|
||||
different people in 10 different locations by the time it reached its
|
||||
destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The
|
||||
process was slow, inefficient and cost a fortune - and was entirely
|
||||
different depending on the facility and the type of goods.
|
||||
|
||||
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
|
||||
Similarly, before Standard Containers, by the time a software
|
||||
component ran in production, it had been individually built,
|
||||
configured, bundled, documented, patched, vendored, templated, tweaked
|
||||
and instrumented by 10 different people on 10 different
|
||||
computers. Builds failed, libraries conflicted, mirrors crashed,
|
||||
post-it notes were lost, logs were misplaced, cluster updates were
|
||||
half-broken. The process was slow, inefficient and cost a fortune -
|
||||
and was entirely different depending on the language and
|
||||
infrastructure provider.
|
||||
|
||||
|
||||
### 5. INDUSTRIAL-GRADE DELIVERY
|
||||
|
||||
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded onto the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
|
||||
There are 17 million shipping containers in existence, packed with
|
||||
every physical good imaginable. Every single one of them can be loaded
|
||||
onto the same boats, by the same cranes, in the same facilities, and
|
||||
sent anywhere in the World with incredible efficiency. It is
|
||||
embarrassing to think that a 30 ton shipment of coffee can safely
|
||||
travel half-way across the World in *less time* than it takes a
|
||||
software team to deliver its code from one datacenter to another
|
||||
sitting 10 miles away.
|
||||
|
||||
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
||||
With Standard Containers we can put an end to that embarrassment, by
|
||||
making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
||||
|
||||
|
||||
|
||||
|
||||
Standard Container Specification
|
||||
--------------------------------
|
||||
|
||||
(TODO)
|
||||
|
||||
### Image format
|
||||
|
||||
|
||||
### Standard operations
|
||||
|
||||
* Copy
|
||||
* Run
|
||||
* Stop
|
||||
* Wait
|
||||
* Commit
|
||||
* Attach standard streams
|
||||
* List filesystem changes
|
||||
* ...
|
||||
|
||||
### Execution environment
|
||||
|
||||
#### Root filesystem
|
||||
|
||||
#### Environment variables
|
||||
|
||||
#### Process arguments
|
||||
|
||||
#### Networking
|
||||
|
||||
#### Process namespacing
|
||||
|
||||
#### Resource limits
|
||||
|
||||
#### Process monitoring
|
||||
|
||||
#### Logging
|
||||
|
||||
#### Signals
|
||||
|
||||
#### Pseudo-terminal allocation
|
||||
|
||||
#### Security
|
||||
|
||||
### Legal
|
||||
|
||||
Transfers of Docker shall be in accordance with applicable export controls of any country and all other applicable
|
||||
legal requirements. Docker shall not be distributed or downloaded to or in Cuba, Iran, North Korea, Sudan or Syria
|
||||
and shall not be distributed or downloaded to any person on the Denied Persons List administered by the U.S.
|
||||
Transfers of Docker shall be in accordance with applicable export
|
||||
controls of any country and all other applicable legal requirements.
|
||||
Docker shall not be distributed or downloaded to or in Cuba, Iran,
|
||||
North Korea, Sudan or Syria and shall not be distributed or downloaded
|
||||
to any person on the Denied Persons List administered by the U.S.
|
||||
Department of Commerce.
|
||||
|
||||
|
||||
Vendored
+8
-8
@@ -82,15 +82,15 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
end
|
||||
|
||||
if !FORWARD_DOCKER_PORTS.nil?
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.forward_port port, port
|
||||
end
|
||||
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.forward_port port, port
|
||||
end
|
||||
end
|
||||
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.network :forwarded_port, :host => port, :guest => port
|
||||
end
|
||||
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||
(49000..49900).each do |port|
|
||||
config.vm.network :forwarded_port, :host => port, :guest => port
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.net/websocket"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
@@ -9,17 +10,22 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const APIVERSION = 1.3
|
||||
const DEFAULTHTTPHOST string = "127.0.0.1"
|
||||
const DEFAULTHTTPPORT int = 4243
|
||||
const APIVERSION = 1.4
|
||||
const DEFAULTHTTPHOST = "127.0.0.1"
|
||||
const DEFAULTHTTPPORT = 4243
|
||||
const DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
|
||||
type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
||||
|
||||
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
@@ -81,24 +87,12 @@ func getBoolParam(value string) (bool, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version > 1.1 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
authConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
func matchesContentType(contentType, expectedType string) bool {
|
||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
if err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
utils.Debugf("Error parsing media type: %s error: %s", contentType, err.Error())
|
||||
}
|
||||
b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
return err == nil && mimetype == expectedType
|
||||
}
|
||||
|
||||
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
@@ -107,28 +101,9 @@ func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reque
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := ""
|
||||
if version > 1.1 {
|
||||
status, err = auth.Login(authConfig, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil {
|
||||
if err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if authConfig.Username == localAuthConfig.Username {
|
||||
authConfig.Password = localAuthConfig.Password
|
||||
}
|
||||
|
||||
newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
|
||||
status, err = auth.Login(newAuthConfig, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := auth.Login(authConfig, srv.HTTPRequestFactory())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != "" {
|
||||
b, err := json.Marshal(&APIAuth{Status: status})
|
||||
@@ -217,6 +192,63 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error {
|
||||
b, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("JSON error")
|
||||
}
|
||||
_, err = wf.Write(b)
|
||||
if err != nil {
|
||||
// On error, evict the listener
|
||||
utils.Debugf("%s", err)
|
||||
srv.Lock()
|
||||
delete(srv.listeners, r.RemoteAddr)
|
||||
srv.Unlock()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
listener := make(chan utils.JSONMessage)
|
||||
srv.Lock()
|
||||
srv.listeners[r.RemoteAddr] = listener
|
||||
srv.Unlock()
|
||||
since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0)
|
||||
if err != nil {
|
||||
since = 0
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
wf := utils.NewWriteFlusher(w)
|
||||
if since != 0 {
|
||||
// If since, send previous events that happened after the timestamp
|
||||
for _, event := range srv.events {
|
||||
if event.Time >= since {
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for event := range listener {
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
@@ -252,11 +284,18 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r
|
||||
}
|
||||
|
||||
func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version < 1.4 {
|
||||
return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
name := vars["name"]
|
||||
procsStr, err := srv.ContainerTop(name)
|
||||
ps_args := r.Form.Get("ps_args")
|
||||
procsStr, err := srv.ContainerTop(name, ps_args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -360,7 +399,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version > 1.0)
|
||||
if image != "" { //pull
|
||||
if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil {
|
||||
if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}, version > 1.3); err != nil {
|
||||
if sf.Used() {
|
||||
w.Write(sf.FormatError(err))
|
||||
return nil
|
||||
@@ -429,16 +468,8 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||
|
||||
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
authConfig := &auth.AuthConfig{}
|
||||
if version > 1.1 {
|
||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil && err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
authConfig = localAuthConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
@@ -470,7 +501,12 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||
resolvConf, err := utils.GetResolvConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
|
||||
config.Dns = defaultDns
|
||||
}
|
||||
@@ -490,6 +526,11 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
|
||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
|
||||
}
|
||||
|
||||
if srv.runtime.capabilities.IPv4ForwardingDisabled {
|
||||
log.Println("Warning: IPv4 forwarding is disabled.")
|
||||
out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
|
||||
}
|
||||
|
||||
b, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -571,7 +612,7 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
|
||||
|
||||
// allow a nil body for backwards compatibility
|
||||
if r.Body != nil {
|
||||
if r.Header.Get("Content-Type") == "application/json" {
|
||||
if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
||||
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -709,6 +750,53 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return nil
|
||||
}
|
||||
|
||||
func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
logs, err := getBoolParam(r.Form.Get("logs"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stream, err := getBoolParam(r.Form.Get("stream"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdin, err := getBoolParam(r.Form.Get("stdin"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := getBoolParam(r.Form.Get("stdout"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := getBoolParam(r.Form.Get("stderr"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
if _, err := srv.ContainerInspect(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := websocket.Handler(func(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
|
||||
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil {
|
||||
utils.Debugf("Error: %s", err)
|
||||
}
|
||||
})
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
@@ -775,12 +863,8 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
remoteURL := r.FormValue("remote")
|
||||
repoName := r.FormValue("t")
|
||||
rawSuppressOutput := r.FormValue("q")
|
||||
tag := ""
|
||||
if strings.Contains(repoName, ":") {
|
||||
remoteParts := strings.Split(repoName, ":")
|
||||
tag = remoteParts[1]
|
||||
repoName = remoteParts[0]
|
||||
}
|
||||
rawNoCache := r.FormValue("nocache")
|
||||
repoName, tag := utils.ParseRepositoryTag(repoName)
|
||||
|
||||
var context io.Reader
|
||||
|
||||
@@ -826,8 +910,12 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noCache, err := getBoolParam(rawNoCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput)
|
||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput, !noCache)
|
||||
id, err := b.Build(context)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error build: %s\n", err)
|
||||
@@ -839,6 +927,36 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
copyData := &APICopy{}
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType == "application/json" {
|
||||
if err := json.NewDecoder(r.Body).Decode(copyData); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Content-Type not supported: %s", contentType)
|
||||
}
|
||||
|
||||
if copyData.Resource == "" {
|
||||
return fmt.Errorf("Resource cannot be empty")
|
||||
}
|
||||
if copyData.Resource[0] == '/' {
|
||||
copyData.Resource = copyData.Resource[1:]
|
||||
}
|
||||
|
||||
if err := srv.ContainerCopy(name, copyData.Resource, w); err != nil {
|
||||
utils.Debugf("%s", err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
@@ -849,25 +967,61 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
||||
}
|
||||
|
||||
func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// log the request
|
||||
utils.Debugf("Calling %s %s", localMethod, localRoute)
|
||||
|
||||
if logging {
|
||||
log.Println(r.Method, r.RequestURI)
|
||||
}
|
||||
|
||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
||||
if len(userAgent) == 2 && userAgent[1] != VERSION {
|
||||
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
|
||||
}
|
||||
}
|
||||
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
|
||||
if err != nil {
|
||||
version = APIVERSION
|
||||
}
|
||||
if srv.enableCors {
|
||||
writeCorsHeaders(w, r)
|
||||
}
|
||||
|
||||
if version == 0 || version > APIVERSION {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := handlerFunc(srv, version, w, r, mux.Vars(r)); err != nil {
|
||||
utils.Debugf("Error: %s", err)
|
||||
httpError(w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
r := mux.NewRouter()
|
||||
|
||||
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
|
||||
m := map[string]map[string]HttpApiFunc{
|
||||
"GET": {
|
||||
"/auth": getAuth,
|
||||
"/version": getVersion,
|
||||
"/info": getInfo,
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
"/containers/ps": getContainersJSON,
|
||||
"/containers/json": getContainersJSON,
|
||||
"/containers/{name:.*}/export": getContainersExport,
|
||||
"/containers/{name:.*}/changes": getContainersChanges,
|
||||
"/containers/{name:.*}/json": getContainersByName,
|
||||
"/containers/{name:.*}/top": getContainersTop,
|
||||
"/events": getEvents,
|
||||
"/info": getInfo,
|
||||
"/version": getVersion,
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
"/containers/ps": getContainersJSON,
|
||||
"/containers/json": getContainersJSON,
|
||||
"/containers/{name:.*}/export": getContainersExport,
|
||||
"/containers/{name:.*}/changes": getContainersChanges,
|
||||
"/containers/{name:.*}/json": getContainersByName,
|
||||
"/containers/{name:.*}/top": getContainersTop,
|
||||
"/containers/{name:.*}/attach/ws": wsContainersAttach,
|
||||
},
|
||||
"POST": {
|
||||
"/auth": postAuth,
|
||||
@@ -886,6 +1040,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/containers/{name:.*}/wait": postContainersWait,
|
||||
"/containers/{name:.*}/resize": postContainersResize,
|
||||
"/containers/{name:.*}/attach": postContainersAttach,
|
||||
"/containers/{name:.*}/copy": postContainersCopy,
|
||||
},
|
||||
"DELETE": {
|
||||
"/containers/{name:.*}": deleteContainers,
|
||||
@@ -901,37 +1056,13 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
utils.Debugf("Registering %s, %s", method, route)
|
||||
// NOTE: scope issue, make sure the variables are local and won't be changed
|
||||
localRoute := route
|
||||
localMethod := method
|
||||
localFct := fct
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.Debugf("Calling %s %s from %s", localMethod, localRoute, r.RemoteAddr)
|
||||
localMethod := method
|
||||
|
||||
if logging {
|
||||
log.Println(r.Method, r.RequestURI)
|
||||
}
|
||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
||||
if len(userAgent) == 2 && userAgent[1] != VERSION {
|
||||
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
|
||||
}
|
||||
}
|
||||
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
|
||||
if err != nil {
|
||||
version = APIVERSION
|
||||
}
|
||||
if srv.enableCors {
|
||||
writeCorsHeaders(w, r)
|
||||
}
|
||||
if version == 0 || version > APIVERSION {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
|
||||
httpError(w, err)
|
||||
}
|
||||
}
|
||||
// build the handler function
|
||||
f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct)
|
||||
|
||||
// add the new route
|
||||
if localRoute == "" {
|
||||
r.Methods(localMethod).HandlerFunc(f)
|
||||
} else {
|
||||
@@ -940,6 +1071,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -954,9 +1086,26 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
|
||||
if proto == "unix" {
|
||||
os.Chmod(addr, 0777)
|
||||
if err := os.Chmod(addr, 0660); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groups, err := ioutil.ReadFile("/etc/group")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
|
||||
if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
|
||||
gid, err := strconv.Atoi(gidMatch[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("docker group found. gid: %d", gid)
|
||||
if err := os.Chown(addr, 0, gid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
httpSrv := http.Server{Addr: addr, Handler: r}
|
||||
return httpSrv.Serve(l)
|
||||
|
||||
+19
-11
@@ -17,20 +17,23 @@ type APIImages struct {
|
||||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
IPv4Forwarding bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
NEventsListener int `json:",omitempty"`
|
||||
KernelVersion string `json:",omitempty"`
|
||||
IndexServerAddress string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APITop struct {
|
||||
PID string
|
||||
Tty string
|
||||
Time string
|
||||
Cmd string
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
type APIRmi struct {
|
||||
@@ -85,3 +88,8 @@ type APIImageConfig struct {
|
||||
ID string `json:"Id"`
|
||||
*Config
|
||||
}
|
||||
|
||||
type APICopy struct {
|
||||
Resource string
|
||||
HostPath string
|
||||
}
|
||||
|
||||
+171
-18
@@ -89,6 +89,44 @@ func TestGetInfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEvents(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
events: make([]utils.JSONMessage, 0, 64),
|
||||
listeners: make(map[string]chan utils.JSONMessage),
|
||||
}
|
||||
|
||||
srv.LogEvent("fakeaction", "fakeid", "fakeimage")
|
||||
srv.LogEvent("fakeaction2", "fakeid", "fakeimage")
|
||||
|
||||
req, err := http.NewRequest("GET", "/events?since=1", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
setTimeout(t, "", 500*time.Millisecond, func() {
|
||||
if err := getEvents(srv, APIVERSION, r, req, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
dec := json.NewDecoder(r.Body)
|
||||
for i := 0; i < 2; i++ {
|
||||
var jm utils.JSONMessage
|
||||
if err := dec.Decode(&jm); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jm != srv.events[i] {
|
||||
t.Fatalf("Event received it different than expected")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetImagesJSON(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
@@ -423,45 +461,76 @@ func TestGetContainersTop(t *testing.T) {
|
||||
|
||||
container, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "sleep 2"},
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
defer func() {
|
||||
// Make sure the process dies before destroying runtime
|
||||
container.stdin.Close()
|
||||
container.WaitTimeout(2 * time.Second)
|
||||
}()
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Give some time to the process to start
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
if container.State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
if !container.State.Running {
|
||||
t.Errorf("Container should be running")
|
||||
t.Fatalf("Container should be running")
|
||||
}
|
||||
|
||||
// Make sure sh spawn up cat
|
||||
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
|
||||
in, _ := container.StdinPipe()
|
||||
out, _ := container.StdoutPipe()
|
||||
if err := assertPipe("hello\n", "hello", out, in, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
if err := getContainersTop(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
|
||||
req, err := http.NewRequest("GET", "/"+container.ID+"/top?ps_args=u", bytes.NewReader([]byte{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
procs := []APITop{}
|
||||
if err := getContainersTop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
procs := APITop{}
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(procs) != 2 {
|
||||
t.Fatalf("Expected 2 processes, found %d.", len(procs))
|
||||
if len(procs.Titles) != 11 {
|
||||
t.Fatalf("Expected 11 titles, found %d.", len(procs.Titles))
|
||||
}
|
||||
if procs.Titles[0] != "USER" || procs.Titles[10] != "COMMAND" {
|
||||
t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.Titles[0], procs.Titles[10])
|
||||
}
|
||||
|
||||
if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" {
|
||||
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd)
|
||||
if len(procs.Processes) != 2 {
|
||||
t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes))
|
||||
}
|
||||
|
||||
if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" {
|
||||
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd)
|
||||
if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "cat" {
|
||||
t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[0][10])
|
||||
}
|
||||
if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "cat" {
|
||||
t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[1][10])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +563,7 @@ func TestGetContainersByName(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if outContainer.ID != container.ID {
|
||||
t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.ID, outContainer.ID)
|
||||
t.Fatalf("Wrong containers retrieved. Expected %s, received %s", container.ID, outContainer.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,7 +802,7 @@ func TestPostContainersStart(t *testing.T) {
|
||||
|
||||
r = httptest.NewRecorder()
|
||||
if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
|
||||
t.Fatalf("A running containter should be able to be started")
|
||||
t.Fatalf("A running container should be able to be started")
|
||||
}
|
||||
|
||||
if err := container.Kill(); err != nil {
|
||||
@@ -857,6 +926,12 @@ func TestPostContainersAttach(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// Try to avoid the timeout in destroy. Best effort, don't check error
|
||||
defer func() {
|
||||
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
|
||||
container.Kill()
|
||||
}()
|
||||
|
||||
// Attach to it
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
@@ -896,7 +971,7 @@ func TestPostContainersAttach(t *testing.T) {
|
||||
}
|
||||
|
||||
// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
@@ -907,7 +982,7 @@ func TestPostContainersAttach(t *testing.T) {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
// Try to avoid the timeout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
@@ -1089,6 +1164,84 @@ func TestDeleteImages(t *testing.T) {
|
||||
} */
|
||||
}
|
||||
|
||||
func TestJsonContentType(t *testing.T) {
|
||||
if !matchesContentType("application/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !matchesContentType("application/json; charset=utf-8", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if matchesContentType("dockerapplication/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostContainersCopy(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test.txt"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if err := container.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
copyData := APICopy{HostPath: ".", Resource: "/test.txt"}
|
||||
|
||||
jsonData, err := json.Marshal(copyData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/copy", bytes.NewReader(jsonData))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
if err = postContainersCopy(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if r.Code != http.StatusOK {
|
||||
t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
|
||||
}
|
||||
|
||||
found := false
|
||||
for tarReader := tar.NewReader(r.Body); ; {
|
||||
h, err := tarReader.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
if h.Name == "test.txt" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("The created test file has not been found in the copied output")
|
||||
}
|
||||
}
|
||||
|
||||
// Mocked types for tests
|
||||
type NopConn struct {
|
||||
io.ReadCloser
|
||||
|
||||
+2
-2
@@ -98,7 +98,7 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `path`.
|
||||
// The archive may be compressed with one of the following algorithgms:
|
||||
// The archive may be compressed with one of the following algorithms:
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
||||
func Untar(archive io.Reader, path string) error {
|
||||
@@ -173,7 +173,7 @@ func CopyWithTar(src, dst string) error {
|
||||
}
|
||||
// Create dst, copy src's content into it
|
||||
utils.Debugf("Creating dest directory: %s", dst)
|
||||
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
|
||||
if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
||||
|
||||
+64
-53
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -18,26 +19,22 @@ const CONFIGFILE = ".dockercfg"
|
||||
// Only used for user auth + account creation
|
||||
const INDEXSERVER = "https://index.docker.io/v1/"
|
||||
|
||||
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
|
||||
//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/"
|
||||
|
||||
var (
|
||||
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
||||
)
|
||||
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Auth string `json:"auth"`
|
||||
Email string `json:"email"`
|
||||
rootPath string
|
||||
}
|
||||
|
||||
func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
|
||||
return &AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
rootPath: rootPath,
|
||||
}
|
||||
type ConfigFile struct {
|
||||
Configs map[string]AuthConfig `json:"configs,omitempty"`
|
||||
rootPath string
|
||||
}
|
||||
|
||||
func IndexServerAddress() string {
|
||||
@@ -54,61 +51,89 @@ func encodeAuth(authConfig *AuthConfig) string {
|
||||
}
|
||||
|
||||
// decode the auth string
|
||||
func decodeAuth(authStr string) (*AuthConfig, error) {
|
||||
func decodeAuth(authStr string) (string, string, error) {
|
||||
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||
decoded := make([]byte, decLen)
|
||||
authByte := []byte(authStr)
|
||||
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", "", err
|
||||
}
|
||||
if n > decLen {
|
||||
return nil, fmt.Errorf("Something went wrong decoding auth config")
|
||||
return "", "", fmt.Errorf("Something went wrong decoding auth config")
|
||||
}
|
||||
arr := strings.Split(string(decoded), ":")
|
||||
if len(arr) != 2 {
|
||||
return nil, fmt.Errorf("Invalid auth configuration file")
|
||||
return "", "", fmt.Errorf("Invalid auth configuration file")
|
||||
}
|
||||
password := strings.Trim(arr[1], "\x00")
|
||||
return &AuthConfig{Username: arr[0], Password: password}, nil
|
||||
return arr[0], password, nil
|
||||
}
|
||||
|
||||
// load up the auth config information and return values
|
||||
// FIXME: use the internal golang config parser
|
||||
func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
func LoadConfig(rootPath string) (*ConfigFile, error) {
|
||||
configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
|
||||
return &configFile, nil //missing file is not an error
|
||||
}
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &configFile, err
|
||||
}
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return nil, fmt.Errorf("The Auth config file is empty")
|
||||
|
||||
if err := json.Unmarshal(b, &configFile.Configs); err != nil {
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return &configFile, fmt.Errorf("The Auth config file is empty")
|
||||
}
|
||||
authConfig := AuthConfig{}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return &configFile, err
|
||||
}
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
authConfig.Email = origEmail[1]
|
||||
configFile.Configs[IndexServerAddress()] = authConfig
|
||||
} else {
|
||||
for k, authConfig := range configFile.Configs {
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||
if err != nil {
|
||||
return &configFile, err
|
||||
}
|
||||
authConfig.Auth = ""
|
||||
configFile.Configs[k] = authConfig
|
||||
}
|
||||
}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
authConfig, err := decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authConfig.Email = origEmail[1]
|
||||
authConfig.rootPath = rootPath
|
||||
return authConfig, nil
|
||||
return &configFile, nil
|
||||
}
|
||||
|
||||
// save the auth config
|
||||
func SaveConfig(authConfig *AuthConfig) error {
|
||||
confFile := path.Join(authConfig.rootPath, CONFIGFILE)
|
||||
if len(authConfig.Email) == 0 {
|
||||
func SaveConfig(configFile *ConfigFile) error {
|
||||
confFile := path.Join(configFile.rootPath, CONFIGFILE)
|
||||
if len(configFile.Configs) == 0 {
|
||||
os.Remove(confFile)
|
||||
return nil
|
||||
}
|
||||
lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n"
|
||||
b := []byte(lines)
|
||||
err := ioutil.WriteFile(confFile, b, 0600)
|
||||
|
||||
configs := make(map[string]AuthConfig, len(configFile.Configs))
|
||||
for k, authConfig := range configFile.Configs {
|
||||
authCopy := authConfig
|
||||
|
||||
authCopy.Auth = encodeAuth(&authCopy)
|
||||
authCopy.Username = ""
|
||||
authCopy.Password = ""
|
||||
|
||||
configs[k] = authCopy
|
||||
}
|
||||
|
||||
b, err := json.Marshal(configs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(confFile, b, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,8 +141,7 @@ func SaveConfig(authConfig *AuthConfig) error {
|
||||
}
|
||||
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig *AuthConfig, store bool) (string, error) {
|
||||
storeConfig := false
|
||||
func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
|
||||
client := &http.Client{}
|
||||
reqStatusCode := 0
|
||||
var status string
|
||||
@@ -143,13 +167,12 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
|
||||
if reqStatusCode == 201 {
|
||||
status = "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
storeConfig = true
|
||||
} else if reqStatusCode == 403 {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please check your e-mail for a confirmation link.")
|
||||
} else if reqStatusCode == 400 {
|
||||
if string(reqBody) == "\"Username or email already exists\"" {
|
||||
req, err := http.NewRequest("GET", IndexServerAddress()+"users/", nil)
|
||||
req, err := factory.NewRequest("GET", IndexServerAddress()+"users/", nil)
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -162,14 +185,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
status = "Login Succeeded"
|
||||
storeConfig = true
|
||||
} else if resp.StatusCode == 401 {
|
||||
if store {
|
||||
authConfig.Email = ""
|
||||
if err := SaveConfig(authConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("Wrong login/password, please try again")
|
||||
} else {
|
||||
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
|
||||
@@ -181,10 +197,5 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
|
||||
} else {
|
||||
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
|
||||
}
|
||||
if storeConfig && store {
|
||||
if err := SaveConfig(authConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
+45
-6
@@ -3,6 +3,7 @@ package auth
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -11,7 +12,9 @@ import (
|
||||
func TestEncodeAuth(t *testing.T) {
|
||||
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
||||
authStr := encodeAuth(newAuthConfig)
|
||||
decAuthConfig, err := decodeAuth(authStr)
|
||||
decAuthConfig := &AuthConfig{}
|
||||
var err error
|
||||
decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -29,8 +32,8 @@ func TestEncodeAuth(t *testing.T) {
|
||||
func TestLogin(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
|
||||
status, err := Login(authConfig, false)
|
||||
authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
|
||||
status, err := Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -49,8 +52,8 @@ func TestCreateAccount(t *testing.T) {
|
||||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
|
||||
status, err := Login(authConfig, false)
|
||||
authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"}
|
||||
status, err := Login(authConfig, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -60,7 +63,7 @@ func TestCreateAccount(t *testing.T) {
|
||||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
|
||||
status, err = Login(authConfig, false)
|
||||
status, err = Login(authConfig, nil)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but found nil instead")
|
||||
}
|
||||
@@ -71,3 +74,39 @@ func TestCreateAccount(t *testing.T) {
|
||||
t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameAuthDataPostSave(t *testing.T) {
|
||||
root, err := ioutil.TempDir("", "docker-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
configFile := &ConfigFile{
|
||||
rootPath: root,
|
||||
Configs: make(map[string]AuthConfig, 1),
|
||||
}
|
||||
|
||||
configFile.Configs["testIndex"] = AuthConfig{
|
||||
Username: "docker-user",
|
||||
Password: "docker-pass",
|
||||
Email: "docker@docker.io",
|
||||
}
|
||||
|
||||
err = SaveConfig(configFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
authConfig := configFile.Configs["testIndex"]
|
||||
if authConfig.Username != "docker-user" {
|
||||
t.Fail()
|
||||
}
|
||||
if authConfig.Password != "docker-pass" {
|
||||
t.Fail()
|
||||
}
|
||||
if authConfig.Email != "docker@docker.io" {
|
||||
t.Fail()
|
||||
}
|
||||
if authConfig.Auth != "" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -38,7 +38,9 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
|
||||
MergeConfig(config, img.Config)
|
||||
}
|
||||
|
||||
if config.Cmd == nil || len(config.Cmd) == 0 {
|
||||
if len(config.Entrypoint) != 0 && config.Cmd == nil {
|
||||
config.Cmd = []string{}
|
||||
} else if config.Cmd == nil || len(config.Cmd) == 0 {
|
||||
return nil, fmt.Errorf("No command specified")
|
||||
}
|
||||
|
||||
@@ -80,7 +82,12 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||
resolvConf, err := utils.GetResolvConf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
|
||||
builder.runtime.Dns = defaultDns
|
||||
}
|
||||
@@ -124,6 +131,10 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
|
||||
func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
|
||||
// FIXME: freeze the container before copying it to avoid data corruption?
|
||||
// FIXME: this shouldn't be in commands.
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rwTar, err := container.ExportRw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+134
-41
@@ -7,9 +7,11 @@ import (
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -24,11 +26,12 @@ type buildFile struct {
|
||||
builder *Builder
|
||||
srv *Server
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
context string
|
||||
verbose bool
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
context string
|
||||
verbose bool
|
||||
utilizeCache bool
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
@@ -53,7 +56,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
if err != nil {
|
||||
if b.runtime.graph.IsNotExist(err) {
|
||||
remote, tag := utils.ParseRepositoryTag(name)
|
||||
if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil {
|
||||
if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
image, err = b.runtime.repositories.LookupImage(name)
|
||||
@@ -66,6 +69,9 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
}
|
||||
b.image = image.ID
|
||||
b.config = &Config{}
|
||||
if b.config.Env == nil || len(b.config.Env) == 0 {
|
||||
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -87,17 +93,21 @@ func (b *buildFile) CmdRun(args string) error {
|
||||
b.config.Cmd = nil
|
||||
MergeConfig(b.config, config)
|
||||
|
||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||
|
||||
utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
||||
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
if b.utilizeCache {
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
}
|
||||
|
||||
cid, err := b.run()
|
||||
@@ -107,10 +117,44 @@ func (b *buildFile) CmdRun(args string) error {
|
||||
if err := b.commit(cid, cmd, "run"); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) FindEnvKey(key string) int {
|
||||
for k, envVar := range b.config.Env {
|
||||
envParts := strings.SplitN(envVar, "=", 2)
|
||||
if key == envParts[0] {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (b *buildFile) ReplaceEnvMatches(value string) (string, error) {
|
||||
exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
matches := exp.FindAllString(value, -1)
|
||||
for _, match := range matches {
|
||||
match = match[strings.Index(match, "$"):]
|
||||
matchKey := strings.Trim(match, "${}")
|
||||
|
||||
for _, envVar := range b.config.Env {
|
||||
envParts := strings.SplitN(envVar, "=", 2)
|
||||
envKey := envParts[0]
|
||||
envValue := envParts[1]
|
||||
|
||||
if envKey == matchKey {
|
||||
value = strings.Replace(value, match, envValue, -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdEnv(args string) error {
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
@@ -119,14 +163,19 @@ func (b *buildFile) CmdEnv(args string) error {
|
||||
key := strings.Trim(tmp[0], " \t")
|
||||
value := strings.Trim(tmp[1], " \t")
|
||||
|
||||
for i, elem := range b.config.Env {
|
||||
if strings.HasPrefix(elem, key+"=") {
|
||||
b.config.Env[i] = key + "=" + value
|
||||
return nil
|
||||
}
|
||||
envKey := b.FindEnvKey(key)
|
||||
replacedValue, err := b.ReplaceEnvMatches(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Env = append(b.config.Env, key+"="+value)
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
|
||||
replacedVar := fmt.Sprintf("%s=%s", key, replacedValue)
|
||||
|
||||
if envKey >= 0 {
|
||||
b.config.Env[envKey] = replacedVar
|
||||
} else {
|
||||
b.config.Env = append(b.config.Env, replacedVar)
|
||||
}
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdCmd(args string) error {
|
||||
@@ -148,6 +197,11 @@ func (b *buildFile) CmdExpose(args string) error {
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdUser(args string) error {
|
||||
b.config.User = args
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdInsert(args string) error {
|
||||
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
|
||||
}
|
||||
@@ -173,6 +227,11 @@ func (b *buildFile) CmdEntrypoint(args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdWorkdir(workdir string) error {
|
||||
b.config.WorkingDir = workdir
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdVolume(args string) error {
|
||||
if args == "" {
|
||||
return fmt.Errorf("Volume cannot be empty")
|
||||
@@ -201,6 +260,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error {
|
||||
}
|
||||
defer file.Body.Close()
|
||||
|
||||
// If the destination is a directory, figure out the filename.
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
u, err := url.Parse(orig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := u.Path
|
||||
if strings.HasSuffix(path, "/") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
parts := strings.Split(path, "/")
|
||||
filename := parts[len(parts)-1]
|
||||
if filename == "" {
|
||||
return fmt.Errorf("cannot determine filename from url: %s", u)
|
||||
}
|
||||
dest = dest + filename
|
||||
}
|
||||
|
||||
return container.Inject(file.Body, dest)
|
||||
}
|
||||
|
||||
@@ -208,9 +285,12 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
||||
origPath := path.Join(b.context, orig)
|
||||
destPath := path.Join(container.RootfsPath(), dest)
|
||||
// Preserve the trailing '/'
|
||||
if dest[len(dest)-1] == '/' {
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
if !strings.HasPrefix(origPath, b.context) {
|
||||
return fmt.Errorf("Forbidden path: %s", origPath)
|
||||
}
|
||||
fi, err := os.Stat(origPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -223,7 +303,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
||||
} else if err := UntarPath(origPath, destPath); err != nil {
|
||||
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
|
||||
// If that fails, just copy it as a regular file
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||
@@ -241,8 +321,16 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ADD format")
|
||||
}
|
||||
orig := strings.Trim(tmp[0], " \t")
|
||||
dest := strings.Trim(tmp[1], " \t")
|
||||
|
||||
orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
||||
@@ -327,16 +415,19 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
if b.utilizeCache {
|
||||
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
||||
return err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.out, " ---> Using cache\n")
|
||||
utils.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return nil
|
||||
} else {
|
||||
utils.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
}
|
||||
|
||||
container, err := b.builder.Create(b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -407,15 +498,16 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
}
|
||||
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
stepN += 1
|
||||
// FIXME: only count known instructions as build steps
|
||||
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
|
||||
|
||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||
if !exists {
|
||||
fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
continue
|
||||
}
|
||||
|
||||
stepN += 1
|
||||
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
|
||||
|
||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||
if ret != nil {
|
||||
return "", ret.(error)
|
||||
@@ -427,10 +519,10 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
|
||||
return b.image, nil
|
||||
}
|
||||
return "", fmt.Errorf("An error occured during the build\n")
|
||||
return "", fmt.Errorf("An error occurred during the build\n")
|
||||
}
|
||||
|
||||
func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
|
||||
func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache bool) BuildFile {
|
||||
return &buildFile{
|
||||
builder: NewBuilder(srv.runtime),
|
||||
runtime: srv.runtime,
|
||||
@@ -440,5 +532,6 @@ func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
|
||||
tmpContainers: make(map[string]struct{}),
|
||||
tmpImages: make(map[string]struct{}),
|
||||
verbose: verbose,
|
||||
utilizeCache: utilizeCache,
|
||||
}
|
||||
}
|
||||
|
||||
+309
-40
@@ -3,13 +3,17 @@ package docker
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// mkTestContext generates a build context from the contents of the provided dockerfile.
|
||||
// This context is suitable for use as an argument to BuildFile.Build()
|
||||
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
|
||||
context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files)
|
||||
context, err := mkBuildContext(dockerfile, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -22,6 +26,8 @@ type testContextTemplate struct {
|
||||
dockerfile string
|
||||
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
|
||||
files [][2]string
|
||||
// Additional remote files to host on a local HTTP server.
|
||||
remoteFiles [][2]string
|
||||
}
|
||||
|
||||
// A table of all the contexts to build and test.
|
||||
@@ -29,27 +35,31 @@ type testContextTemplate struct {
|
||||
var testContexts = []testContextTemplate{
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
|
||||
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
add foo /usr/lib/bla/bar
|
||||
run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
|
||||
run [ "$(cat /usr/lib/bla/bar)" = 'hello' ]
|
||||
add http://{SERVERADDR}/baz /usr/lib/baz/quux
|
||||
run [ "$(cat /usr/lib/baz/quux)" = 'world!' ]
|
||||
`,
|
||||
[][2]string{{"foo", "hello world!"}},
|
||||
[][2]string{{"foo", "hello"}},
|
||||
[][2]string{{"/baz", "world!"}},
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
add f /
|
||||
run [ "$(cat /f)" = "hello" ]
|
||||
add f /abc
|
||||
@@ -71,59 +81,156 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
|
||||
{"f", "hello"},
|
||||
{"d/ga", "bu"},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
add http://{SERVERADDR}/x /a/b/c
|
||||
run [ "$(cat /a/b/c)" = "hello" ]
|
||||
add http://{SERVERADDR}/x?foo=bar /
|
||||
run [ "$(cat /x)" = "hello" ]
|
||||
add http://{SERVERADDR}/x /d/
|
||||
run [ "$(cat /d/x)" = "hello" ]
|
||||
add http://{SERVERADDR} /e
|
||||
run [ "$(cat /e)" = "blah" ]
|
||||
`,
|
||||
nil,
|
||||
[][2]string{{"/x", "hello"}, {"/", "blah"}},
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
env FOO BAR
|
||||
run [ "$FOO" = "BAR" ]
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
ENTRYPOINT /bin/echo
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
ENTRYPOINT /bin/echo
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
VOLUME /test
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
env FOO /foo/baz
|
||||
env BAR /bar
|
||||
env BAZ $BAR
|
||||
env FOOPATH $PATH:$FOO
|
||||
run [ "$BAR" = "$BAZ" ]
|
||||
run [ "$FOOPATH" = "$PATH:/foo/baz" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
env FOO /bar
|
||||
env TEST testdir
|
||||
env BAZ /foobar
|
||||
add testfile $BAZ/
|
||||
add $TEST $FOO
|
||||
run [ "$(cat /foobar/testfile)" = "test1" ]
|
||||
run [ "$(cat /bar/withfile)" = "test2" ]
|
||||
`,
|
||||
[][2]string{
|
||||
{"testfile", "test1"},
|
||||
{"testdir/withfile", "test2"},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
// FIXME: test building with 2 successive overlapping ADD commands
|
||||
|
||||
func constructDockerfile(template string, ip net.IP, port string) string {
|
||||
serverAddr := fmt.Sprintf("%s:%s", ip, port)
|
||||
replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
|
||||
return replacer.Replace(template)
|
||||
}
|
||||
|
||||
func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
|
||||
mux := http.NewServeMux()
|
||||
for _, file := range files {
|
||||
name, contents := file[0], file[1]
|
||||
mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(contents))
|
||||
})
|
||||
}
|
||||
|
||||
// This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote
|
||||
// connections (from the container).
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := httptest.NewUnstartedServer(mux)
|
||||
s.Listener = listener
|
||||
s.Start()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
for _, ctx := range testContexts {
|
||||
buildImage(ctx, t)
|
||||
buildImage(ctx, t, nil, true)
|
||||
}
|
||||
}
|
||||
|
||||
func buildImage(context testContextTemplate, t *testing.T) *Image {
|
||||
runtime, err := newTestRuntime()
|
||||
func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image {
|
||||
if srv == nil {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv = &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
defer httpServer.Close()
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
idx := strings.LastIndex(httpServer.URL, ":")
|
||||
if idx < 0 {
|
||||
t.Fatalf("could not get port from test http server address %s", httpServer.URL)
|
||||
}
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false)
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t))
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, useCache)
|
||||
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -137,10 +244,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
||||
|
||||
func TestVolume(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
volume /test
|
||||
cmd Hello world
|
||||
`, nil}, t)
|
||||
`, nil, nil}, t, nil, true)
|
||||
|
||||
if len(img.Config.Volumes) == 0 {
|
||||
t.Fail()
|
||||
@@ -154,33 +261,50 @@ func TestVolume(t *testing.T) {
|
||||
|
||||
func TestBuildMaintainer(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
`, nil}, t)
|
||||
`, nil, nil}, t, nil, true)
|
||||
|
||||
if img.Author != "dockerio" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUser(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
user dockerio
|
||||
`, nil, nil}, t, nil, true)
|
||||
|
||||
if img.Config.User != "dockerio" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEnv(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
env port 4243
|
||||
`,
|
||||
nil}, t)
|
||||
|
||||
if img.Config.Env[0] != "port=4243" {
|
||||
nil, nil}, t, nil, true)
|
||||
hasEnv := false
|
||||
for _, envVar := range img.Config.Env {
|
||||
if envVar == "port=4243" {
|
||||
hasEnv = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasEnv {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCmd(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
cmd ["/bin/echo", "Hello World"]
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t, nil, true)
|
||||
|
||||
if img.Config.Cmd[0] != "/bin/echo" {
|
||||
t.Log(img.Config.Cmd[0])
|
||||
@@ -194,10 +318,10 @@ func TestBuildCmd(t *testing.T) {
|
||||
|
||||
func TestBuildExpose(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
expose 4243
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t, nil, true)
|
||||
|
||||
if img.Config.PortSpecs[0] != "4243" {
|
||||
t.Fail()
|
||||
@@ -206,11 +330,156 @@ func TestBuildExpose(t *testing.T) {
|
||||
|
||||
func TestBuildEntrypoint(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t, nil, true)
|
||||
|
||||
if img.Config.Entrypoint[0] != "/bin/echo" {
|
||||
}
|
||||
}
|
||||
|
||||
// testing #1405 - config.Cmd does not get cleaned up if
|
||||
// utilizing cache
|
||||
func TestBuildEntrypointRunCleanup(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
img := buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
run echo "hello"
|
||||
`,
|
||||
nil, nil}, t, srv, true)
|
||||
|
||||
img = buildImage(testContextTemplate{`
|
||||
from {IMAGE}
|
||||
run echo "hello"
|
||||
add foo /foo
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
[][2]string{{"foo", "HEYO"}}, nil}, t, srv, true)
|
||||
|
||||
if len(img.Config.Cmd) != 0 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageWithCache(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
template := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
`,
|
||||
nil, nil}
|
||||
|
||||
img := buildImage(template, t, srv, true)
|
||||
imageId := img.ID
|
||||
|
||||
img = nil
|
||||
img = buildImage(template, t, srv, true)
|
||||
|
||||
if imageId != img.ID {
|
||||
t.Logf("Image ids should match: %s != %s", imageId, img.ID)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageWithoutCache(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
template := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
`,
|
||||
nil, nil}
|
||||
|
||||
img := buildImage(template, t, srv, true)
|
||||
imageId := img.ID
|
||||
|
||||
img = nil
|
||||
img = buildImage(template, t, srv, false)
|
||||
|
||||
if imageId == img.ID {
|
||||
t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbiddenContextPath(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
context := testContextTemplate{`
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
add ../../ test/
|
||||
`,
|
||||
[][2]string{{"test.txt", "test1"}, {"other.txt", "other"}}, nil}
|
||||
|
||||
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer httpServer.Close()
|
||||
|
||||
idx := strings.LastIndex(httpServer.URL, ":")
|
||||
if idx < 0 {
|
||||
t.Fatalf("could not get port from test http server address %s", httpServer.URL)
|
||||
}
|
||||
port := httpServer.URL[idx+1:]
|
||||
|
||||
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false, true)
|
||||
_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||
|
||||
if err == nil {
|
||||
t.Log("Error should not be nil")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if err.Error() != "Forbidden path: /" {
|
||||
t.Logf("Error message is not expected: %s", err.Error())
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
+235
-89
@@ -27,10 +27,9 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.5.0"
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
VERSION string
|
||||
)
|
||||
|
||||
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
|
||||
@@ -72,12 +71,14 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
|
||||
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTUNIXSOCKET)
|
||||
for _, command := range [][]string{
|
||||
{"attach", "Attach to a running container"},
|
||||
{"build", "Build a container from a Dockerfile"},
|
||||
{"commit", "Create a new image from a container's changes"},
|
||||
{"cp", "Copy files/folders from the containers filesystem to the host path"},
|
||||
{"diff", "Inspect changes on a container's filesystem"},
|
||||
{"events", "Get real time events from the server"},
|
||||
{"export", "Stream the contents of a container as a tar archive"},
|
||||
{"history", "Show the history of an image"},
|
||||
{"images", "List images"},
|
||||
@@ -157,9 +158,9 @@ func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
|
||||
|
||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
||||
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
|
||||
tag := cmd.String("t", "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
|
||||
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
|
||||
|
||||
noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -185,14 +186,17 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
|
||||
isRemote = true
|
||||
} else {
|
||||
if _, err := os.Stat(cmd.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
context, err = Tar(cmd.Arg(0), Uncompressed)
|
||||
}
|
||||
var body io.Reader
|
||||
// Setup an upload progress bar
|
||||
// FIXME: ProgressReader shouldn't be this annoyning to use
|
||||
// FIXME: ProgressReader shouldn't be this annoying to use
|
||||
if context != nil {
|
||||
sf := utils.NewStreamFormatter(false)
|
||||
body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
|
||||
body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("", "Uploading context", "%v bytes%0.0s%0.0s"), sf, true)
|
||||
}
|
||||
// Upload the build context
|
||||
v := &url.Values{}
|
||||
@@ -204,6 +208,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
if isRemote {
|
||||
v.Set("remote", cmd.Arg(0))
|
||||
}
|
||||
if *noCache {
|
||||
v.Set("nocache", "1")
|
||||
}
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -295,6 +302,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
var oldState *term.State
|
||||
if *flUsername == "" || *flPassword == "" || *flEmail == "" {
|
||||
oldState, err = term.SetRawTerminal(cli.terminalFd)
|
||||
@@ -310,16 +319,29 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
email string
|
||||
)
|
||||
|
||||
var promptDefault = func(prompt string, configDefault string) {
|
||||
if configDefault == "" {
|
||||
fmt.Fprintf(cli.out, "%s: ", prompt)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
|
||||
}
|
||||
}
|
||||
|
||||
authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()]
|
||||
if !ok {
|
||||
authconfig = auth.AuthConfig{}
|
||||
}
|
||||
|
||||
if *flUsername == "" {
|
||||
fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username)
|
||||
promptDefault("Username", authconfig.Username)
|
||||
username = readAndEchoString(cli.in, cli.out)
|
||||
if username == "" {
|
||||
username = cli.authConfig.Username
|
||||
username = authconfig.Username
|
||||
}
|
||||
} else {
|
||||
username = *flUsername
|
||||
}
|
||||
if username != cli.authConfig.Username {
|
||||
if username != authconfig.Username {
|
||||
if *flPassword == "" {
|
||||
fmt.Fprintf(cli.out, "Password: ")
|
||||
password = readString(cli.in, cli.out)
|
||||
@@ -331,31 +353,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
}
|
||||
|
||||
if *flEmail == "" {
|
||||
fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email)
|
||||
promptDefault("Email", authconfig.Email)
|
||||
email = readAndEchoString(cli.in, cli.out)
|
||||
if email == "" {
|
||||
email = cli.authConfig.Email
|
||||
email = authconfig.Email
|
||||
}
|
||||
} else {
|
||||
email = *flEmail
|
||||
}
|
||||
} else {
|
||||
password = cli.authConfig.Password
|
||||
email = cli.authConfig.Email
|
||||
password = authconfig.Password
|
||||
email = authconfig.Email
|
||||
}
|
||||
if oldState != nil {
|
||||
term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
}
|
||||
cli.authConfig.Username = username
|
||||
cli.authConfig.Password = password
|
||||
cli.authConfig.Email = email
|
||||
authconfig.Username = username
|
||||
authconfig.Password = password
|
||||
authconfig.Email = email
|
||||
cli.configFile.Configs[auth.IndexServerAddress()] = authconfig
|
||||
|
||||
body, statusCode, err := cli.call("POST", "/auth", cli.authConfig)
|
||||
body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()])
|
||||
if statusCode == 401 {
|
||||
cli.authConfig.Username = ""
|
||||
cli.authConfig.Password = ""
|
||||
cli.authConfig.Email = ""
|
||||
auth.SaveConfig(cli.authConfig)
|
||||
delete(cli.configFile.Configs, auth.IndexServerAddress())
|
||||
auth.SaveConfig(cli.configFile)
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
@@ -365,10 +386,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
var out2 APIAuth
|
||||
err = json.Unmarshal(body, &out2)
|
||||
if err != nil {
|
||||
auth.LoadConfig(os.Getenv("HOME"))
|
||||
cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME"))
|
||||
return err
|
||||
}
|
||||
auth.SaveConfig(cli.authConfig)
|
||||
auth.SaveConfig(cli.configFile)
|
||||
if out2.Status != "" {
|
||||
fmt.Fprintf(cli.out, "%s\n", out2.Status)
|
||||
}
|
||||
@@ -432,6 +453,15 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||
if out.GoVersion != "" {
|
||||
fmt.Fprintf(cli.out, "Go version: %s\n", out.GoVersion)
|
||||
}
|
||||
|
||||
release := utils.GetReleaseVersion()
|
||||
if release != "" {
|
||||
fmt.Fprintf(cli.out, "Last stable version: %s", release)
|
||||
if strings.Trim(VERSION, "-dev") != release || strings.Trim(out.Version, "-dev") != release {
|
||||
fmt.Fprintf(cli.out, ", please update docker")
|
||||
}
|
||||
fmt.Fprintf(cli.out, "\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -463,6 +493,18 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
||||
fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd)
|
||||
fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines)
|
||||
fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion)
|
||||
fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
|
||||
fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion)
|
||||
}
|
||||
|
||||
if len(out.IndexServerAddress) != 0 {
|
||||
cli.LoadConfigFile()
|
||||
u := cli.configFile.Configs[out.IndexServerAddress].Username
|
||||
if len(u) > 0 {
|
||||
fmt.Fprintf(cli.out, "Username: %v\n", u)
|
||||
fmt.Fprintf(cli.out, "Registry: %v\n", out.IndexServerAddress)
|
||||
}
|
||||
}
|
||||
if !out.MemoryLimit {
|
||||
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
||||
@@ -470,12 +512,15 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
if !out.SwapLimit {
|
||||
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
||||
}
|
||||
if !out.IPv4Forwarding {
|
||||
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -585,23 +630,28 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
if cmd.NArg() != 1 {
|
||||
if cmd.NArg() == 0 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top", nil)
|
||||
val := url.Values{}
|
||||
if cmd.NArg() > 1 {
|
||||
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
||||
}
|
||||
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var procs []APITop
|
||||
procs := APITop{}
|
||||
err = json.Unmarshal(body, &procs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
fmt.Fprintln(w, "PID\tTTY\tTIME\tCMD")
|
||||
for _, proc := range procs {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", proc.PID, proc.Tty, proc.Time, proc.Cmd)
|
||||
fmt.Fprintln(w, strings.Join(procs.Titles, "\t"))
|
||||
for _, proc := range procs.Processes {
|
||||
fmt.Fprintln(w, strings.Join(proc, "\t"))
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
@@ -756,7 +806,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
|
||||
cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -790,25 +840,38 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := cli.checkIfLogged("push"); err != nil {
|
||||
return err
|
||||
}
|
||||
cli.LoadConfigFile()
|
||||
|
||||
// If we're not using a custom registry, we know the restrictions
|
||||
// applied to repository names and can warn the user in advance.
|
||||
// Custom repositories can have different rules, and we must also
|
||||
// allow pushing by image ID.
|
||||
if len(strings.SplitN(name, "/", 2)) == 1 {
|
||||
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(cli.authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
username := cli.configFile.Configs[auth.IndexServerAddress()].Username
|
||||
if username == "" {
|
||||
username = "<user>"
|
||||
}
|
||||
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
|
||||
push := func() error {
|
||||
buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out)
|
||||
}
|
||||
|
||||
if err := push(); err != nil {
|
||||
if err.Error() == "Authentication is required." {
|
||||
fmt.Fprintln(cli.out, "\nPlease login prior to push:")
|
||||
if err := cli.CmdLogin(""); err != nil {
|
||||
return err
|
||||
}
|
||||
return push()
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -1052,6 +1115,29 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
||||
since := cmd.String("since", "", "Show events previously created (used for polling).")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cmd.NArg() != 0 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
if *since != "" {
|
||||
v.Set("since", *since)
|
||||
}
|
||||
|
||||
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
@@ -1105,10 +1191,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, cli.out); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, cli.err); err != nil {
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -1311,10 +1394,29 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var containerIDFile *os.File
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
||||
return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
|
||||
}
|
||||
containerIDFile, err = os.Create(hostConfig.ContainerIDFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the container ID file: %s", err)
|
||||
}
|
||||
defer containerIDFile.Close()
|
||||
}
|
||||
|
||||
//create the container
|
||||
body, statusCode, err := cli.call("POST", "/containers/create", config)
|
||||
//if image not found try to pull it
|
||||
if statusCode == 404 {
|
||||
_, tag := utils.ParseRepositoryTag(config.Image)
|
||||
if tag == "" {
|
||||
tag = DEFAULTTAG
|
||||
}
|
||||
|
||||
fmt.Printf("Unable to find image '%s' (tag: %s) locally\n", config.Image, tag)
|
||||
|
||||
v := url.Values{}
|
||||
repos, tag := utils.ParseRepositoryTag(config.Image)
|
||||
v.Set("fromImage", repos)
|
||||
@@ -1341,6 +1443,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
for _, warning := range runResult.Warnings {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
|
||||
}
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
|
||||
return fmt.Errorf("failed to write the container ID to the file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
//start the container
|
||||
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
||||
@@ -1361,25 +1468,39 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||
if config.Tty {
|
||||
if err := cli.monitorTtySize(runResult.ID); err != nil {
|
||||
return err
|
||||
utils.Debugf("Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("logs", "1")
|
||||
v.Set("stream", "1")
|
||||
var out io.Writer
|
||||
|
||||
if config.AttachStdin {
|
||||
v.Set("stdin", "1")
|
||||
}
|
||||
if config.AttachStdout {
|
||||
v.Set("stdout", "1")
|
||||
out = cli.out
|
||||
}
|
||||
if config.AttachStderr {
|
||||
v.Set("stderr", "1")
|
||||
out = cli.out
|
||||
}
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
for sig := range signals {
|
||||
fmt.Printf("\nReceived signal: %s; cleaning up\n", sig)
|
||||
if err := cli.CmdStop("-t", "4", runResult.ID); err != nil {
|
||||
fmt.Printf("failed to stop container: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out); err != nil {
|
||||
utils.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
@@ -1391,15 +1512,33 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) checkIfLogged(action string) error {
|
||||
// If condition AND the login failed
|
||||
if cli.authConfig.Username == "" {
|
||||
if err := cli.CmdLogin(""); err != nil {
|
||||
func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
cmd := Subcmd("cp", "CONTAINER:RESOURCE HOSTPATH", "Copy files/folders from the RESOURCE to the HOSTPATH")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cmd.NArg() != 2 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
var copyData APICopy
|
||||
info := strings.Split(cmd.Arg(0), ":")
|
||||
|
||||
copyData.Resource = info[1]
|
||||
copyData.HostPath = cmd.Arg(1)
|
||||
|
||||
data, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if statusCode == 200 {
|
||||
r := bytes.NewReader(data)
|
||||
if err := Untar(r, copyData.HostPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if cli.authConfig.Username == "" {
|
||||
return fmt.Errorf("Please login prior to %s. ('docker login')", action)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1419,6 +1558,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
||||
return nil, -1, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
|
||||
req.Host = cli.addr
|
||||
if data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if method == "POST" {
|
||||
@@ -1426,6 +1566,9 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
||||
}
|
||||
dial, err := net.Dial(cli.proto, cli.addr)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||
}
|
||||
return nil, -1, err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
@@ -1460,11 +1603,15 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
|
||||
req.Host = cli.addr
|
||||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
dial, err := net.Dial(cli.proto, cli.addr)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||
}
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
@@ -1489,23 +1636,8 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
||||
return fmt.Errorf("Error: %s", body)
|
||||
}
|
||||
|
||||
if resp.Header.Get("Content-Type") == "application/json" {
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var m utils.JSONMessage
|
||||
if err := dec.Decode(&m); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Progress != "" {
|
||||
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
|
||||
} else if m.Error != "" {
|
||||
return fmt.Errorf(m.Error)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n", m.Status)
|
||||
}
|
||||
}
|
||||
if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, out)
|
||||
} else {
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
@@ -1522,9 +1654,13 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
req.Host = cli.addr
|
||||
|
||||
dial, err := net.Dial(cli.proto, cli.addr)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||
}
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
@@ -1536,10 +1672,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
|
||||
receiveStdout := utils.Go(func() error {
|
||||
_, err := io.Copy(out, br)
|
||||
return err
|
||||
})
|
||||
var receiveStdout (chan error)
|
||||
if out != nil {
|
||||
receiveStdout = utils.Go(func() error {
|
||||
_, err := io.Copy(out, br)
|
||||
utils.Debugf("[hijack] End of stdout")
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
|
||||
oldState, err := term.SetRawTerminal(cli.terminalFd)
|
||||
@@ -1552,6 +1692,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
sendStdin := utils.Go(func() error {
|
||||
if in != nil {
|
||||
io.Copy(rwc, in)
|
||||
utils.Debugf("[hijack] End of stdin")
|
||||
}
|
||||
if tcpc, ok := rwc.(*net.TCPConn); ok {
|
||||
if err := tcpc.CloseWrite(); err != nil {
|
||||
@@ -1566,9 +1707,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := <-receiveStdout; err != nil {
|
||||
utils.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
if out != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
utils.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !cli.isTerminal {
|
||||
@@ -1614,13 +1757,11 @@ func (cli *DockerCli) monitorTtySize(id string) error {
|
||||
}
|
||||
cli.resizeTty(id)
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGWINCH)
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigchan, syscall.SIGWINCH)
|
||||
go func() {
|
||||
for sig := range c {
|
||||
if sig == syscall.SIGWINCH {
|
||||
cli.resizeTty(id)
|
||||
}
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
@@ -1636,6 +1777,14 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
return flags
|
||||
}
|
||||
|
||||
func (cli *DockerCli) LoadConfigFile() (err error) {
|
||||
cli.configFile, err = auth.LoadConfig(os.Getenv("HOME"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
|
||||
var (
|
||||
isTerminal = false
|
||||
@@ -1652,12 +1801,9 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
|
||||
if err == nil {
|
||||
err = out
|
||||
}
|
||||
|
||||
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
|
||||
return &DockerCli{
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
authConfig: authConfig,
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
@@ -1669,7 +1815,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
|
||||
type DockerCli struct {
|
||||
proto string
|
||||
addr string
|
||||
authConfig *auth.AuthConfig
|
||||
configFile *auth.ConfigFile
|
||||
in io.ReadCloser
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
|
||||
+293
-11
@@ -38,7 +38,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
|
||||
f()
|
||||
c <- false
|
||||
}()
|
||||
if <-c {
|
||||
if <-c && msg != "" {
|
||||
t.Fatal(msg)
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
|
||||
func TestRunHostname(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
@@ -74,7 +73,7 @@ func TestRunHostname(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
utils.Debugf("--")
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
@@ -91,6 +90,219 @@ func TestRunHostname(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
|
||||
func TestRunWorkdir(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-w", "/foo/bar", unitTestImageID, "pwd"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != "/foo/bar\n" {
|
||||
t.Fatalf("'pwd' should display '%s', not '%s'", "/foo/bar\n", cmdOutput)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
|
||||
func TestRunWorkdirExists(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdRun("-w", "/proc", unitTestImageID, "pwd"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != "/proc\n" {
|
||||
t.Fatalf("'pwd' should display '%s', not '%s'", "/proc\n", cmdOutput)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
func TestRunExit(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
cli.CmdRun("-i", unitTestImageID, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
// Closing /bin/cat stdin, expect it to exit
|
||||
if err := stdin.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the process exited, CmdRun must finish and unblock. Wait for it
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
|
||||
<-c1
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Make sure that the client has been disconnected
|
||||
setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
|
||||
// Expecting pipe i/o error, just check that read does not block
|
||||
stdin.Read([]byte{})
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnect(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
cli.CmdRun("-i", unitTestImageID, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the pipes are close, we expect the process to die,
|
||||
// therefore CmdRun to unblock. Wait for CmdRun
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should cause stdin to be closed, which should
|
||||
// cause /bin/cat to exit.
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := globalRuntime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnectTty(t *testing.T) {
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
}
|
||||
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// In tty mode, we expect the process to stay alive even after client's stdin closes.
|
||||
// Do not wait for run to finish
|
||||
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAttachStdin checks attaching to stdin without stdout and stderr.
|
||||
// 'docker run -i -a stdin' should sends the client's stdin to the command,
|
||||
@@ -106,7 +318,7 @@ func TestRunAttachStdin(t *testing.T) {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat")
|
||||
cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat && sleep 5")
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin
|
||||
@@ -134,26 +346,96 @@ func TestRunAttachStdin(t *testing.T) {
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
|
||||
// Unblock hijack end
|
||||
stdout.Read([]byte{})
|
||||
<-ch
|
||||
})
|
||||
|
||||
setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() {
|
||||
setTimeout(t, "Waiting for command to exit timed out", 10*time.Second, func() {
|
||||
container.Wait()
|
||||
})
|
||||
|
||||
// Check logs
|
||||
if cmdLogs, err := container.ReadLog("stdout"); err != nil {
|
||||
if cmdLogs, err := container.ReadLog("json"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if output, err := ioutil.ReadAll(cmdLogs); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expectedLog := "hello\nhi there\n"
|
||||
if string(output) != expectedLog {
|
||||
t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output)
|
||||
expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
|
||||
for _, expectedLog := range expectedLogs {
|
||||
if !strings.Contains(string(output), expectedLog) {
|
||||
t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour, the process stays alive when the client disconnects
|
||||
func TestAttachDisconnect(t *testing.T) {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
go func() {
|
||||
// Start a process in daemon mode
|
||||
if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
// Attach to it
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdAttach returns.
|
||||
cli.CmdAttach(container.ID)
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
// Close pipes (client disconnects)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// We closed stdin, expect /bin/cat to still be running
|
||||
// Wait a little bit to make sure container.monitor() did his thing
|
||||
err := container.WaitTimeout(500 * time.Millisecond)
|
||||
if err == nil || !container.State.Running {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
// Try to avoid the timeout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
+187
-82
@@ -2,6 +2,7 @@ package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/term"
|
||||
@@ -52,35 +53,40 @@ type Container struct {
|
||||
|
||||
waitLock chan struct{}
|
||||
Volumes map[string]string
|
||||
// Store rw/ro in a separate structure to preserve reserve-compatibility on-disk.
|
||||
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
||||
// Easier than migrating older container configs :)
|
||||
VolumesRW map[string]bool
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
CpuShares int64 // CPU shares (relative weight vs. other containers)
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string
|
||||
Cmd []string
|
||||
Dns []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
Entrypoint []string
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
CpuShares int64 // CPU shares (relative weight vs. other containers)
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string
|
||||
Cmd []string
|
||||
Dns []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
WorkingDir string
|
||||
Entrypoint []string
|
||||
NetworkDisabled bool
|
||||
Privileged bool
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
Binds []string
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
}
|
||||
|
||||
type BindMap struct {
|
||||
@@ -89,20 +95,34 @@ type BindMap struct {
|
||||
Mode string
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
|
||||
)
|
||||
|
||||
type KeyValuePair struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
||||
if len(args) > 0 && args[0] != "--help" {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.Usage = nil
|
||||
}
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
|
||||
flUser := cmd.String("u", "", "Username or UID")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||
flAttach := NewAttachOpts()
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
||||
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
@@ -126,12 +146,18 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
|
||||
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
|
||||
var flLxcOpts ListOpts
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
|
||||
}
|
||||
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
||||
return nil, nil, cmd, ErrInvaidWorikingDirectory
|
||||
}
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
@@ -170,27 +196,38 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
entrypoint = []string{*flEntrypoint}
|
||||
}
|
||||
|
||||
var lxcConf []KeyValuePair
|
||||
lxcConf, err := parseLxcConfOpts(flLxcOpts)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: *flHostname,
|
||||
PortSpecs: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: *flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: flEnv,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: *flVolumesFrom,
|
||||
Entrypoint: entrypoint,
|
||||
Hostname: *flHostname,
|
||||
PortSpecs: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: *flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: flEnv,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: *flVolumesFrom,
|
||||
Entrypoint: entrypoint,
|
||||
Privileged: *flPrivileged,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
}
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
||||
@@ -259,7 +296,8 @@ func (container *Container) FromDisk() error {
|
||||
return err
|
||||
}
|
||||
// Load container settings
|
||||
if err := json.Unmarshal(data, container); err != nil {
|
||||
// udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it
|
||||
if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -293,7 +331,7 @@ func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) {
|
||||
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
|
||||
}
|
||||
|
||||
func (container *Container) generateLXCConfig() error {
|
||||
func (container *Container) generateLXCConfig(hostConfig *HostConfig) error {
|
||||
fo, err := os.Create(container.lxcConfigPath())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -302,6 +340,11 @@ func (container *Container) generateLXCConfig() error {
|
||||
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
|
||||
return err
|
||||
}
|
||||
if hostConfig != nil {
|
||||
if err := LxcHostConfigTemplateCompiled.Execute(fo, hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -372,14 +415,15 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
utils.Debugf("[start] attach stdin\n")
|
||||
defer utils.Debugf("[end] attach stdin\n")
|
||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||
if cStdout != nil {
|
||||
defer cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
defer cStderr.Close()
|
||||
}
|
||||
if container.Config.StdinOnce && !container.Config.Tty {
|
||||
defer cStdin.Close()
|
||||
} else {
|
||||
if cStdout != nil {
|
||||
defer cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
defer cStderr.Close()
|
||||
}
|
||||
}
|
||||
if container.Config.Tty {
|
||||
_, err = utils.CopyEscapable(cStdin, stdin)
|
||||
@@ -497,7 +541,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
|
||||
if len(hostConfig.Binds) == 0 {
|
||||
if len(hostConfig.Binds) == 0 && len(hostConfig.LxcConf) == 0 {
|
||||
hostConfig, _ = container.ReadHostConfig()
|
||||
}
|
||||
|
||||
@@ -507,8 +551,12 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
if container.runtime.networkManager.disabled {
|
||||
container.Config.NetworkDisabled = true
|
||||
} else {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the config is compatible with the current kernel
|
||||
@@ -520,8 +568,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
container.Config.MemorySwap = -1
|
||||
}
|
||||
container.Volumes = make(map[string]string)
|
||||
container.VolumesRW = make(map[string]bool)
|
||||
|
||||
if container.runtime.capabilities.IPv4ForwardingDisabled {
|
||||
log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work")
|
||||
}
|
||||
|
||||
// Create the requested bind mounts
|
||||
binds := make(map[string]BindMap)
|
||||
@@ -559,10 +609,38 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
binds[path.Clean(dst)] = bindMap
|
||||
}
|
||||
|
||||
// FIXME: evaluate volumes-from before individual volumes, so that the latter can override the former.
|
||||
// Create the requested volumes volumes
|
||||
if container.Volumes == nil || len(container.Volumes) == 0 {
|
||||
container.Volumes = make(map[string]string)
|
||||
container.VolumesRW = make(map[string]bool)
|
||||
}
|
||||
|
||||
// Apply volumes from another container if requested
|
||||
if container.Config.VolumesFrom != "" {
|
||||
c := container.runtime.Get(container.Config.VolumesFrom)
|
||||
if c == nil {
|
||||
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID)
|
||||
}
|
||||
for volPath, id := range c.Volumes {
|
||||
if _, exists := container.Volumes[volPath]; exists {
|
||||
continue
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
container.Volumes[volPath] = id
|
||||
if isRW, exists := c.VolumesRW[volPath]; exists {
|
||||
container.VolumesRW[volPath] = isRW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the requested volumes if they don't exist
|
||||
for volPath := range container.Config.Volumes {
|
||||
volPath = path.Clean(volPath)
|
||||
// Skip existing volumes
|
||||
if _, exists := container.Volumes[volPath]; exists {
|
||||
continue
|
||||
}
|
||||
// If an external bind is defined for this volume, use that as a source
|
||||
if bindMap, exists := binds[volPath]; exists {
|
||||
container.Volumes[volPath] = bindMap.SrcPath
|
||||
@@ -588,26 +666,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.VolumesFrom != "" {
|
||||
c := container.runtime.Get(container.Config.VolumesFrom)
|
||||
if c == nil {
|
||||
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID)
|
||||
}
|
||||
for volPath, id := range c.Volumes {
|
||||
if _, exists := container.Volumes[volPath]; exists {
|
||||
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.ID)
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
container.Volumes[volPath] = id
|
||||
if isRW, exists := c.VolumesRW[volPath]; exists {
|
||||
container.VolumesRW[volPath] = isRW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := container.generateLXCConfig(); err != nil {
|
||||
if err := container.generateLXCConfig(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -615,11 +674,13 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
"-n", container.ID,
|
||||
"-f", container.lxcConfigPath(),
|
||||
"--",
|
||||
"/sbin/init",
|
||||
"/.dockerinit",
|
||||
}
|
||||
|
||||
// Networking
|
||||
params = append(params, "-g", container.network.Gateway.String())
|
||||
if !container.Config.NetworkDisabled {
|
||||
params = append(params, "-g", container.network.Gateway.String())
|
||||
}
|
||||
|
||||
// User
|
||||
if container.Config.User != "" {
|
||||
@@ -634,7 +695,21 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
params = append(params,
|
||||
"-e", "HOME=/",
|
||||
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"-e", "container=lxc",
|
||||
"-e", "HOSTNAME="+container.Config.Hostname,
|
||||
)
|
||||
if container.Config.WorkingDir != "" {
|
||||
workingDir := path.Clean(container.Config.WorkingDir)
|
||||
utils.Debugf("[working dir] working dir is %s", workingDir)
|
||||
|
||||
if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
params = append(params,
|
||||
"-w", workingDir,
|
||||
)
|
||||
}
|
||||
|
||||
for _, elem := range container.Config.Env {
|
||||
params = append(params, "-e", elem)
|
||||
@@ -647,10 +722,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
container.cmd = exec.Command("lxc-start", params...)
|
||||
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
|
||||
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
|
||||
if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -709,17 +784,21 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
||||
|
||||
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stdout.AddWriter(writer)
|
||||
container.stdout.AddWriter(writer, "")
|
||||
return utils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stderr.AddWriter(writer)
|
||||
container.stderr.AddWriter(writer, "")
|
||||
return utils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) allocateNetwork() error {
|
||||
if container.Config.NetworkDisabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
iface, err := container.runtime.networkManager.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -746,6 +825,9 @@ func (container *Container) allocateNetwork() error {
|
||||
}
|
||||
|
||||
func (container *Container) releaseNetwork() {
|
||||
if container.Config.NetworkDisabled {
|
||||
return
|
||||
}
|
||||
container.network.Release()
|
||||
container.network = nil
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
@@ -781,7 +863,9 @@ func (container *Container) monitor() {
|
||||
}
|
||||
}
|
||||
utils.Debugf("Process finished")
|
||||
|
||||
if container.runtime != nil && container.runtime.srv != nil {
|
||||
container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image))
|
||||
}
|
||||
exitCode := -1
|
||||
if container.cmd != nil {
|
||||
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
@@ -1060,3 +1144,24 @@ func (container *Container) GetSize() (int64, int64) {
|
||||
}
|
||||
return sizeRw, sizeRootfs
|
||||
}
|
||||
|
||||
func (container *Container) Copy(resource string) (Archive, error) {
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var filter []string
|
||||
basePath := path.Join(container.RootfsPath(), resource)
|
||||
stat, err := os.Stat(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
d, f := path.Split(basePath)
|
||||
basePath = d
|
||||
filter = []string{f}
|
||||
} else {
|
||||
filter = []string{path.Base(basePath)}
|
||||
basePath = path.Dir(basePath)
|
||||
}
|
||||
return TarFilter(basePath, Uncompressed, filter)
|
||||
}
|
||||
|
||||
+265
-117
@@ -39,16 +39,11 @@ func TestIDFormat(t *testing.T) {
|
||||
func TestMultipleAttachRestart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c",
|
||||
"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
|
||||
},
|
||||
container, hostConfig, _ := mkContainer(
|
||||
runtime,
|
||||
[]string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
|
||||
t,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
// Simulate 3 client attaching to the container and stop/restart
|
||||
@@ -65,7 +60,6 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -140,19 +134,8 @@ func TestMultipleAttachRestart(t *testing.T) {
|
||||
func TestDiff(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
// Create a container and remove a file
|
||||
container1, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/rm", "/etc/passwd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if err := container1.Run(); err != nil {
|
||||
@@ -185,15 +168,7 @@ func TestDiff(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create a new container from the commited image
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.ID,
|
||||
Cmd: []string{"cat", "/etc/passwd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
if err := container2.Run(); err != nil {
|
||||
@@ -211,16 +186,8 @@ func TestDiff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new containere
|
||||
container3, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"rm", "/bin/httpd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create a new container
|
||||
container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
|
||||
defer runtime.Destroy(container3)
|
||||
|
||||
if err := container3.Run(); err != nil {
|
||||
@@ -246,17 +213,7 @@ func TestDiff(t *testing.T) {
|
||||
func TestCommitAutoRun(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
container1, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
@@ -279,14 +236,7 @@ func TestCommitAutoRun(t *testing.T) {
|
||||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.ID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t)
|
||||
defer runtime.Destroy(container2)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -296,7 +246,6 @@ func TestCommitAutoRun(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -324,17 +273,7 @@ func TestCommitRun(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
container1, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
@@ -357,16 +296,7 @@ func TestCommitRun(t *testing.T) {
|
||||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: img.ID,
|
||||
Cmd: []string{"cat", "/world"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
|
||||
defer runtime.Destroy(container2)
|
||||
stdout, err := container2.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -376,7 +306,6 @@ func TestCommitRun(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container2.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -403,18 +332,7 @@ func TestCommitRun(t *testing.T) {
|
||||
func TestStart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Memory: 33554432,
|
||||
CpuShares: 1000,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
cStdin, err := container.StdinPipe()
|
||||
@@ -422,7 +340,6 @@ func TestStart(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -434,10 +351,10 @@ func TestStart(t *testing.T) {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Start(hostConfig); err == nil {
|
||||
t.Fatalf("A running containter should be able to be started")
|
||||
t.Fatalf("A running container should be able to be started")
|
||||
}
|
||||
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
// Try to avoid the timeout in destroy. Best effort, don't check error
|
||||
cStdin.Close()
|
||||
container.WaitTimeout(2 * time.Second)
|
||||
}
|
||||
@@ -445,15 +362,7 @@ func TestStart(t *testing.T) {
|
||||
func TestRun(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"ls", "-al"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
@@ -492,22 +401,24 @@ func TestOutput(t *testing.T) {
|
||||
func TestKillDifferentUser(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"tail", "-f", "/etc/resolv.conf"},
|
||||
User: "daemon",
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
OpenStdin: true,
|
||||
User: "daemon",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
defer container.stdin.Close()
|
||||
|
||||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(&HostConfig{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -517,8 +428,13 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// Even if the state is running, lets give some time to lxc to spawn the process
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
|
||||
out, _ := container.StdoutPipe()
|
||||
in, _ := container.StdinPipe()
|
||||
if err := assertPipe("hello\n", "hello", out, in, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -855,7 +771,7 @@ func TestUser(t *testing.T) {
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
User: "unkownuser",
|
||||
User: "unknownuser",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1050,6 +966,8 @@ func TestEnv(t *testing.T) {
|
||||
goodEnv := []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"HOME=/",
|
||||
"container=lxc",
|
||||
"HOSTNAME=" + container.ShortID(),
|
||||
}
|
||||
sort.Strings(goodEnv)
|
||||
if len(goodEnv) != len(actualEnv) {
|
||||
@@ -1085,6 +1003,28 @@ func TestEntrypoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntrypointNoCmd(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Entrypoint: []string{"/bin/echo", "foobar"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err := container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Trim(string(output), "\r\n") != "foobar" {
|
||||
t.Error(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func grepFile(t *testing.T, path string, pattern string) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
@@ -1130,7 +1070,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
container.generateLXCConfig()
|
||||
container.generateLXCConfig(nil)
|
||||
grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar")
|
||||
grepFile(t, container.lxcConfigPath(),
|
||||
fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem))
|
||||
@@ -1138,6 +1078,36 @@ func TestLXCConfig(t *testing.T) {
|
||||
fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2))
|
||||
}
|
||||
|
||||
func TestCustomLxcConfig(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true"},
|
||||
|
||||
Hostname: "foobar",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
hostConfig := &HostConfig{LxcConf: []KeyValuePair{
|
||||
{
|
||||
Key: "lxc.utsname",
|
||||
Value: "docker",
|
||||
},
|
||||
{
|
||||
Key: "lxc.cgroup.cpuset.cpus",
|
||||
Value: "0,1",
|
||||
},
|
||||
}}
|
||||
|
||||
container.generateLXCConfig(hostConfig)
|
||||
grepFile(t, container.lxcConfigPath(), "lxc.utsname = docker")
|
||||
grepFile(t, container.lxcConfigPath(), "lxc.cgroup.cpuset.cpus = 0,1")
|
||||
}
|
||||
|
||||
func BenchmarkRunSequencial(b *testing.B) {
|
||||
runtime := mkRuntime(b)
|
||||
defer nuke(runtime)
|
||||
@@ -1241,7 +1211,7 @@ func TestBindMounts(t *testing.T) {
|
||||
readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist
|
||||
|
||||
// test mounting to an illegal destination directory
|
||||
if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil {
|
||||
if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "_", "ls", "."}, nil); err == nil {
|
||||
t.Fatal("Container bind mounted illegal directory")
|
||||
}
|
||||
}
|
||||
@@ -1299,3 +1269,181 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// Test that restarting a container with a volume does not create a new volume on restart. Regression test for #819.
|
||||
func TestRestartWithVolumes(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
for key := range container.Config.Volumes {
|
||||
if key != "/test" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
_, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := container.Volumes["/test"]
|
||||
if expected == "" {
|
||||
t.Fail()
|
||||
}
|
||||
// Run the container again to verify the volume path persists
|
||||
_, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual := container.Volumes["/test"]
|
||||
if expected != actual {
|
||||
t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for #1351
|
||||
func TestVolumesFromWithVolumes(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
for key := range container.Config.Volumes {
|
||||
if key != "/test" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
_, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := container.Volumes["/test"]
|
||||
if expected == "" {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat", "/test/foo"},
|
||||
VolumesFrom: container.ID,
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
output, err := container2.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(output) != "bar" {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if container.Volumes["/test"] != container2.Volumes["/test"] {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Ensure it restarts successfully
|
||||
_, err = container2.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
config, hc, _, err := ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := NewBuilder(runtime).Create(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdout, err := c.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer runtime.Destroy(c)
|
||||
if err := c.Start(hc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.WaitTimeout(500 * time.Millisecond)
|
||||
c.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1)
|
||||
if len(interfaces) != 1 {
|
||||
t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces)
|
||||
}
|
||||
if !strings.HasSuffix(interfaces[0], ": lo") {
|
||||
t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPrivilegedCanMknod(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
if output, _ := runContainer(runtime, []string{"-privileged", "_", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok"}, t); output != "ok\n" {
|
||||
t.Fatal("Could not mknod into privileged container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivilegedCanMount(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
if output, _ := runContainer(runtime, []string{"-privileged", "_", "sh", "-c", "mount -t tmpfs none /tmp && echo ok"}, t); output != "ok\n" {
|
||||
t.Fatal("Could not mount into privileged container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivilegedCannotMknod(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
if output, _ := runContainer(runtime, []string{"_", "sh", "-c", "mknod /tmp/sda b 8 0 || echo ok"}, t); output != "ok\n" {
|
||||
t.Fatal("Could mknod into secure container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivilegedCannotMount(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
if output, _ := runContainer(runtime, []string{"_", "sh", "-c", "mount -t tmpfs none /tmp || echo ok"}, t); output != "ok\n" {
|
||||
t.Fatal("Could mount into secure container")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.pyc
|
||||
@@ -0,0 +1,78 @@
|
||||
# docker-brew
|
||||
|
||||
docker-brew is a command-line tool used to build the docker standard library.
|
||||
|
||||
## Install instructions
|
||||
|
||||
1. Install python if it isn't already available on your OS of choice
|
||||
1. Install the easy_install tool (`sudo apt-get install python-setuptools`
|
||||
for Debian)
|
||||
1. Install the python package manager, `pip` (`easy_install pip`)
|
||||
1. Run the following command: `sudo pip install -r requirements.txt`
|
||||
1. You should now be able to use the `docker-brew` script as such.
|
||||
|
||||
## Basics
|
||||
|
||||
./docker-brew -h
|
||||
|
||||
Display usage and help.
|
||||
|
||||
./docker-brew
|
||||
|
||||
Default build from the default repo/branch. Images will be created under the
|
||||
`library/` namespace. Does not perform a remote push.
|
||||
|
||||
./docker-brew -n mycorp.com -b stable --push git://github.com/mycorp/docker
|
||||
|
||||
Will fetch the library definition files in the `stable` branch of the
|
||||
`git://github.com/mycorp/docker` repository and create images under the
|
||||
`mycorp.com` namespace (e.g. `mycorp.com/ubuntu`). Created images will then
|
||||
be pushed to the official docker repository (pending: support for private
|
||||
repositories)
|
||||
|
||||
## Library definition files
|
||||
|
||||
The library definition files are plain text files found in the `library/`
|
||||
subfolder of the docker repository.
|
||||
|
||||
### File names
|
||||
|
||||
The name of a definition file will determine the name of the image(s) it
|
||||
creates. For example, the `library/ubuntu` file will create images in the
|
||||
`<namespace>/ubuntu` repository. If multiple instructions are present in
|
||||
a single file, all images are expected to be created under a different tag.
|
||||
|
||||
### Instruction format
|
||||
|
||||
Each line represents a build instruction.
|
||||
There are different formats that `docker-brew` is able to parse.
|
||||
|
||||
<git-url>
|
||||
git://github.com/dotcloud/hipache
|
||||
https://github.com/dotcloud/docker.git
|
||||
|
||||
The simplest format. `docker-brew` will fetch data from the provided git
|
||||
repository from the `HEAD`of its `master` branch. Generated image will be
|
||||
tagged as `latest`. Use of this format is discouraged because there is no
|
||||
way to ensure stability.
|
||||
|
||||
<docker-tag> <git-url>
|
||||
bleeding-edge git://github.com/dotcloud/docker
|
||||
unstable https://github.com/dotcloud/docker-redis.git
|
||||
|
||||
A more advanced format. `docker-brew` will fetch data from the provided git
|
||||
repository from the `HEAD`of its `master` branch. Generated image will be
|
||||
tagged as `<docker-tag>`. Recommended if we always want to provide a snapshot
|
||||
of the latest development. Again, no way to ensure stability.
|
||||
|
||||
<docker-tag> <git-url> T:<git-tag>
|
||||
2.4.0 git://github.com/dotcloud/docker-redis T:2.4.0
|
||||
<docker-tag> <git-url> B:<git-branch>
|
||||
zfs git://github.com/dotcloud/docker B:zfs-support
|
||||
<docker-tag> <git-url> C:<git-commit-id>
|
||||
2.2.0 https://github.com/dotcloud/docker-redis.git C:a4bf8923ee4ec566d3ddc212
|
||||
|
||||
The most complete format. `docker-brew` will fetch data from the provided git
|
||||
repository from the provided reference (if it's a branch, brew will fetch its
|
||||
`HEAD`). Generated image will be tagged as `<docker-tag>`. Recommended whenever
|
||||
possible.
|
||||
@@ -0,0 +1 @@
|
||||
from brew import build_library, DEFAULT_REPOSITORY, DEFAULT_BRANCH
|
||||
@@ -0,0 +1,185 @@
|
||||
import os
|
||||
import logging
|
||||
from shutil import rmtree
|
||||
|
||||
import docker
|
||||
|
||||
import git
|
||||
|
||||
DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker'
|
||||
DEFAULT_BRANCH = 'master'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
|
||||
level='INFO')
|
||||
client = docker.Client()
|
||||
processed = {}
|
||||
processed_folders = []
|
||||
|
||||
|
||||
def build_library(repository=None, branch=None, namespace=None, push=False,
|
||||
debug=False, prefill=True, registry=None):
|
||||
dst_folder = None
|
||||
summary = Summary()
|
||||
if repository is None:
|
||||
repository = DEFAULT_REPOSITORY
|
||||
if branch is None:
|
||||
branch = DEFAULT_BRANCH
|
||||
if debug:
|
||||
logger.setLevel('DEBUG')
|
||||
|
||||
if not (repository.startswith('https://') or repository.startswith('git://')):
|
||||
logger.info('Repository provided assumed to be a local path')
|
||||
dst_folder = repository
|
||||
|
||||
try:
|
||||
client.version()
|
||||
except Exception as e:
|
||||
logger.error('Could not reach the docker daemon. Please make sure it '
|
||||
'is running.')
|
||||
logger.warning('Also make sure you have access to the docker UNIX '
|
||||
'socket (use sudo)')
|
||||
return
|
||||
|
||||
#FIXME: set destination folder and only pull latest changes instead of
|
||||
# cloning the whole repo everytime
|
||||
if not dst_folder:
|
||||
logger.info('Cloning docker repo from {0}, branch: {1}'.format(
|
||||
repository, branch))
|
||||
try:
|
||||
rep, dst_folder = git.clone_branch(repository, branch)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
logger.error('Source repository could not be fetched. Check '
|
||||
'that the address is correct and the branch exists.')
|
||||
return
|
||||
try:
|
||||
dirlist = os.listdir(os.path.join(dst_folder, 'library'))
|
||||
except OSError as e:
|
||||
logger.error('The path provided ({0}) could not be found or didn\'t'
|
||||
'contain a library/ folder.'.format(dst_folder))
|
||||
return
|
||||
for buildfile in dirlist:
|
||||
if buildfile == 'MAINTAINERS':
|
||||
continue
|
||||
f = open(os.path.join(dst_folder, 'library', buildfile))
|
||||
linecnt = 0
|
||||
for line in f:
|
||||
linecnt = linecnt + 1
|
||||
logger.debug('{0} ---> {1}'.format(buildfile, line))
|
||||
args = line.split()
|
||||
try:
|
||||
if len(args) > 3:
|
||||
raise RuntimeError('Incorrect line format, '
|
||||
'please refer to the docs')
|
||||
|
||||
url = None
|
||||
ref = 'refs/heads/master'
|
||||
tag = None
|
||||
if len(args) == 1: # Just a URL, simple mode
|
||||
url = args[0]
|
||||
elif len(args) == 2 or len(args) == 3: # docker-tag url
|
||||
url = args[1]
|
||||
tag = args[0]
|
||||
|
||||
if len(args) == 3: # docker-tag url B:branch or T:tag
|
||||
ref = None
|
||||
if args[2].startswith('B:'):
|
||||
ref = 'refs/heads/' + args[2][2:]
|
||||
elif args[2].startswith('T:'):
|
||||
ref = 'refs/tags/' + args[2][2:]
|
||||
elif args[2].startswith('C:'):
|
||||
ref = args[2][2:]
|
||||
else:
|
||||
raise RuntimeError('Incorrect line format, '
|
||||
'please refer to the docs')
|
||||
if prefill:
|
||||
logger.debug('Pulling {0} from official repository (cache '
|
||||
'fill)'.format(buildfile))
|
||||
client.pull(buildfile)
|
||||
img = build_repo(url, ref, buildfile, tag, namespace, push,
|
||||
registry)
|
||||
summary.add_success(buildfile, (linecnt, line), img)
|
||||
processed['{0}@{1}'.format(url, ref)] = img
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
summary.add_exception(buildfile, (linecnt, line), e)
|
||||
|
||||
f.close()
|
||||
if dst_folder != repository:
|
||||
rmtree(dst_folder, True)
|
||||
for d in processed_folders:
|
||||
rmtree(d, True)
|
||||
summary.print_summary(logger)
|
||||
|
||||
|
||||
def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry):
|
||||
docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo)
|
||||
img_id = None
|
||||
dst_folder = None
|
||||
if '{0}@{1}'.format(repository, ref) not in processed.keys():
|
||||
logger.info('Cloning {0} (ref: {1})'.format(repository, ref))
|
||||
if repository not in processed:
|
||||
rep, dst_folder = git.clone(repository, ref)
|
||||
processed[repository] = rep
|
||||
processed_folders.append(dst_folder)
|
||||
else:
|
||||
dst_folder = git.checkout(processed[repository], ref)
|
||||
if not 'Dockerfile' in os.listdir(dst_folder):
|
||||
raise RuntimeError('Dockerfile not found in cloned repository')
|
||||
logger.info('Building using dockerfile...')
|
||||
img_id, logs = client.build(path=dst_folder, quiet=True)
|
||||
else:
|
||||
img_id = processed['{0}@{1}'.format(repository, ref)]
|
||||
logger.info('Committing to {0}:{1}'.format(docker_repo,
|
||||
docker_tag or 'latest'))
|
||||
client.tag(img_id, docker_repo, docker_tag)
|
||||
if push:
|
||||
logger.info('Pushing result to registry {0}'.format(
|
||||
registry or "default"))
|
||||
if registry is not None:
|
||||
docker_repo = '{0}/{1}'.format(registry, docker_repo)
|
||||
logger.info('Also tagging {0}'.format(docker_repo))
|
||||
client.tag(img_id, docker_repo, docker_tag)
|
||||
client.push(docker_repo)
|
||||
return img_id
|
||||
|
||||
|
||||
class Summary(object):
|
||||
def __init__(self):
|
||||
self._summary = {}
|
||||
self._has_exc = False
|
||||
|
||||
def _add_data(self, image, linestr, data):
|
||||
if image not in self._summary:
|
||||
self._summary[image] = { linestr: data }
|
||||
else:
|
||||
self._summary[image][linestr] = data
|
||||
|
||||
def add_exception(self, image, line, exc):
|
||||
lineno, linestr = line
|
||||
self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) })
|
||||
self._has_exc = True
|
||||
|
||||
def add_success(self, image, line, img_id):
|
||||
lineno, linestr = line
|
||||
self._add_data(image, linestr, { 'line': lineno, 'id': img_id })
|
||||
|
||||
def print_summary(self, logger=None):
|
||||
linesep = ''.center(61, '-') + '\n'
|
||||
s = 'BREW BUILD SUMMARY\n' + linesep
|
||||
success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc)
|
||||
details = linesep
|
||||
for image, lines in self._summary.iteritems():
|
||||
details = details + '{}\n{}'.format(image, linesep)
|
||||
for linestr, data in lines.iteritems():
|
||||
details = details + '{0:2} | {1} | {2:50}\n'.format(
|
||||
data['line'],
|
||||
'KO' if 'exc' in data else 'OK',
|
||||
data['exc'] if 'exc' in data else data['id']
|
||||
)
|
||||
details = details + linesep
|
||||
if logger:
|
||||
logger.info(s + success + details)
|
||||
else:
|
||||
print s, success, details
|
||||
@@ -0,0 +1,63 @@
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
from dulwich import index
|
||||
from dulwich.client import get_transport_and_path
|
||||
from dulwich.repo import Repo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def clone_branch(repo_url, branch="master", folder=None):
|
||||
return clone(repo_url, 'refs/heads/' + branch, folder)
|
||||
|
||||
|
||||
def clone_tag(repo_url, tag, folder=None):
|
||||
return clone(repo_url, 'refs/tags/' + tag, folder)
|
||||
|
||||
|
||||
def checkout(rep, ref=None):
|
||||
is_commit = False
|
||||
if ref is None:
|
||||
ref = 'refs/heads/master'
|
||||
elif not ref.startswith('refs/'):
|
||||
is_commit = True
|
||||
if is_commit:
|
||||
rep['HEAD'] = rep.commit(ref)
|
||||
else:
|
||||
rep['HEAD'] = rep.refs[ref]
|
||||
indexfile = rep.index_path()
|
||||
tree = rep["HEAD"].tree
|
||||
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
|
||||
return rep.path
|
||||
|
||||
def clone(repo_url, ref=None, folder=None):
|
||||
is_commit = False
|
||||
if ref is None:
|
||||
ref = 'refs/heads/master'
|
||||
elif not ref.startswith('refs/'):
|
||||
is_commit = True
|
||||
logger.debug("clone repo_url={0}, ref={1}".format(repo_url, ref))
|
||||
if folder is None:
|
||||
folder = tempfile.mkdtemp()
|
||||
logger.debug("folder = {0}".format(folder))
|
||||
rep = Repo.init(folder)
|
||||
client, relative_path = get_transport_and_path(repo_url)
|
||||
logger.debug("client={0}".format(client))
|
||||
|
||||
remote_refs = client.fetch(relative_path, rep)
|
||||
for k, v in remote_refs.iteritems():
|
||||
try:
|
||||
rep.refs.add_if_new(k, v)
|
||||
except:
|
||||
pass
|
||||
|
||||
if is_commit:
|
||||
rep['HEAD'] = rep.commit(ref)
|
||||
else:
|
||||
rep['HEAD'] = remote_refs[ref]
|
||||
indexfile = rep.index_path()
|
||||
tree = rep["HEAD"].tree
|
||||
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
|
||||
logger.debug("done")
|
||||
return rep, folder
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
import brew
|
||||
except ImportError as e:
|
||||
print str(e)
|
||||
print 'Please install the required dependencies first'
|
||||
print 'sudo pip install -r requirements.txt'
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser('Build the docker standard library')
|
||||
parser.add_argument('--push', action='store_true', default=False,
|
||||
help='Push generated repositories')
|
||||
parser.add_argument('--debug', default=False, action='store_true',
|
||||
help='Enable debugging output')
|
||||
parser.add_argument('--noprefill', default=True, action='store_false',
|
||||
dest='prefill', help='Disable cache prefill')
|
||||
parser.add_argument('-n', metavar='NAMESPACE', default='library',
|
||||
help='Namespace used for generated repositories.'
|
||||
' Default is library')
|
||||
parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH,
|
||||
help='Branch in the repository where the library definition'
|
||||
' files will be fetched. Default is ' + brew.DEFAULT_BRANCH)
|
||||
parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY,
|
||||
nargs='?', help='git repository containing the library definition'
|
||||
' files. Default is ' + brew.DEFAULT_REPOSITORY)
|
||||
parser.add_argument('--reg', default=None, help='Registry address to'
|
||||
' push build results to. Also sets push to true.')
|
||||
args = parser.parse_args()
|
||||
brew.build_library(args.repository, args.b, args.n,
|
||||
args.push or args.reg is not None, args.debug, args.prefill, args.reg)
|
||||
@@ -0,0 +1,2 @@
|
||||
dulwich==0.9.0
|
||||
-e git://github.com/dotcloud/docker-py.git#egg=docker-py
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from setuptools import setup
|
||||
|
||||
ROOT_DIR = os.path.dirname(__file__)
|
||||
SOURCE_DIR = os.path.join(ROOT_DIR)
|
||||
|
||||
test_requirements = []
|
||||
setup(
|
||||
name="docker-brew",
|
||||
version='0.0.1',
|
||||
description="-",
|
||||
packages=['dockerbrew'],
|
||||
install_requires=['dulwich', 'docker'] + test_requirements,
|
||||
zip_safe=False,
|
||||
classifiers=['Development Status :: 3 - Alpha',
|
||||
'Environment :: Other Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Utilities'],
|
||||
)
|
||||
@@ -0,0 +1,469 @@
|
||||
#!bash
|
||||
#
|
||||
# bash completion file for core docker commands
|
||||
#
|
||||
# This script provides supports completion of:
|
||||
# - commands and their options
|
||||
# - container ids
|
||||
# - image repos and tags
|
||||
# - filepaths
|
||||
#
|
||||
# To enable the completions either:
|
||||
# - place this file in /etc/bash_completion.d
|
||||
# or
|
||||
# - copy this file and add the line below to your .bashrc after
|
||||
# bash completion features are loaded
|
||||
# . docker.bash
|
||||
#
|
||||
# Note:
|
||||
# Currently, the completions will not work if the docker daemon is not
|
||||
# bound to the default communication port/socket
|
||||
# If the docker daemon is using a unix socket for communication your user
|
||||
# must have access to the socket for the completions to function correctly
|
||||
|
||||
have docker && {
|
||||
__docker_containers_all()
|
||||
{
|
||||
local containers
|
||||
containers="$( docker ps -a -q )"
|
||||
COMPREPLY=( $( compgen -W "$containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_containers_running()
|
||||
{
|
||||
local containers
|
||||
containers="$( docker ps -q )"
|
||||
COMPREPLY=( $( compgen -W "$containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_containers_stopped()
|
||||
{
|
||||
local containers
|
||||
containers="$( comm -13 <(docker ps -q | sort -u) <(docker ps -a -q | sort -u) )"
|
||||
COMPREPLY=( $( compgen -W "$containers" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_image_repos()
|
||||
{
|
||||
local repos
|
||||
repos="$( docker images | awk 'NR>1{print $1}' )"
|
||||
COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) )
|
||||
}
|
||||
|
||||
__docker_images()
|
||||
{
|
||||
local images
|
||||
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
|
||||
COMPREPLY=( $( compgen -W "$images" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags()
|
||||
{
|
||||
local repos images
|
||||
repos="$( docker images | awk 'NR>1{print $1}' )"
|
||||
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
|
||||
COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_containers_and_images()
|
||||
{
|
||||
local containers images
|
||||
containers="$( docker ps -a -q )"
|
||||
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
|
||||
COMPREPLY=( $( compgen -W "$images $containers" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
_docker_docker()
|
||||
{
|
||||
case "$prev" in
|
||||
-H)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-H" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=( $( compgen -W "$commands help" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_attach()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_build()
|
||||
{
|
||||
case "$prev" in
|
||||
-t)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t -q" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_commit()
|
||||
{
|
||||
case "$prev" in
|
||||
-author|-m|-run)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-author -m -run" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_diff()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_events()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-since" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_export()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_help()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
COMPREPLY=( $( compgen -W "$commands" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_history()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_images()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$cpos
|
||||
while [ $counter -le $cword ]; do
|
||||
case "${words[$counter]}" in
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
if [ $counter -eq $cword ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_import()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_info()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_insert()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_inspect()
|
||||
{
|
||||
__docker_containers_and_images
|
||||
}
|
||||
|
||||
_docker_kill()
|
||||
{
|
||||
__docker_containers_running
|
||||
}
|
||||
|
||||
_docker_login()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-e -p -u" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_logs()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_port()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_ps()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-a -beforeId -l -n -notrunc -q -s -sinceId" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_pull()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_push()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_restart()
|
||||
{
|
||||
case "$prev" in
|
||||
-t)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rm()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-v" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_stopped
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rmi()
|
||||
{
|
||||
__docker_image_repos_and_tags
|
||||
}
|
||||
|
||||
_docker_run()
|
||||
{
|
||||
case "$prev" in
|
||||
-cidfile)
|
||||
_filedir
|
||||
;;
|
||||
-volumes-from)
|
||||
__docker_containers_all
|
||||
;;
|
||||
-a|-c|-dns|-e|-entrypoint|-h|-m|-p|-u|-v)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a -c -cidfile -d -dns -e -entrypoint -h -i -m -n -p -t -u -v -volumes-from" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$cpos
|
||||
while [ $counter -le $cword ]; do
|
||||
case "${words[$counter]}" in
|
||||
-a|-c|-cidfile|-dns|-e|-entrypoint|-h|-m|-p|-u|-v|-volumes-from)
|
||||
(( counter++ ))
|
||||
;;
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
if [ $counter -eq $cword ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_search()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-notrunc" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_start()
|
||||
{
|
||||
__docker_containers_stopped
|
||||
}
|
||||
|
||||
_docker_stop()
|
||||
{
|
||||
case "$prev" in
|
||||
-t)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_tag()
|
||||
{
|
||||
COMPREPLY=( $( compgen -W "-f" -- "$cur" ) )
|
||||
}
|
||||
|
||||
_docker_top()
|
||||
{
|
||||
if [ $cpos -eq $cword ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_version()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_wait()
|
||||
{
|
||||
__docker_containers_all
|
||||
}
|
||||
|
||||
_docker()
|
||||
{
|
||||
local cur prev words cword command="docker" counter=1 word cpos
|
||||
local commands="
|
||||
attach
|
||||
build
|
||||
commit
|
||||
diff
|
||||
events
|
||||
export
|
||||
history
|
||||
images
|
||||
import
|
||||
info
|
||||
insert
|
||||
inspect
|
||||
kill
|
||||
login
|
||||
logs
|
||||
port
|
||||
ps
|
||||
pull
|
||||
push
|
||||
restart
|
||||
rm
|
||||
rmi
|
||||
run
|
||||
search
|
||||
start
|
||||
stop
|
||||
tag
|
||||
top
|
||||
version
|
||||
wait
|
||||
"
|
||||
|
||||
COMPREPLY=()
|
||||
_get_comp_words_by_ref -n : cur prev words cword
|
||||
|
||||
while [ $counter -lt $cword ]; do
|
||||
word="${words[$counter]}"
|
||||
case "$word" in
|
||||
-H)
|
||||
(( counter++ ))
|
||||
;;
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
command="$word"
|
||||
cpos=$counter
|
||||
(( cpos++ ))
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
local completions_func=_docker_${command}
|
||||
declare -F $completions_func >/dev/null && $completions_func
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _docker docker
|
||||
}
|
||||
+12
-6
@@ -3,7 +3,7 @@
|
||||
# Original version by Jeff Lindsay <progrium@gmail.com>
|
||||
# Revamped by Jerome Petazzoni <jerome@dotcloud.com>
|
||||
#
|
||||
# This script canonical location is http://get.docker.io/; to update it, run:
|
||||
# This script canonical location is https://get.docker.io/; to update it, run:
|
||||
# s3cmd put -m text/x-shellscript -P install.sh s3://get.docker.io/index
|
||||
|
||||
echo "Ensuring basic dependencies are installed..."
|
||||
@@ -35,17 +35,23 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Downloading docker binary and uncompressing into /usr/local/bin..."
|
||||
curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz |
|
||||
tar -C /usr/local/bin --strip-components=1 -zxf- \
|
||||
docker-latest/docker
|
||||
echo "Downloading docker binary to /usr/local/bin..."
|
||||
curl -s https://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest \
|
||||
> /usr/local/bin/docker
|
||||
chmod +x /usr/local/bin/docker
|
||||
|
||||
if [ -f /etc/init/dockerd.conf ]
|
||||
then
|
||||
echo "Upstart script already exists."
|
||||
else
|
||||
echo "Creating /etc/init/dockerd.conf..."
|
||||
echo "exec env LANG=\"en_US.UTF-8\" /usr/local/bin/docker -d" > /etc/init/dockerd.conf
|
||||
cat >/etc/init/dockerd.conf <<EOF
|
||||
description "Docker daemon"
|
||||
start on filesystem or runlevel [2345]
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec /usr/local/bin/docker -d
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "Starting dockerd..."
|
||||
|
||||
+15
-4
@@ -16,28 +16,34 @@ import (
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
VERSION string
|
||||
)
|
||||
|
||||
func main() {
|
||||
if utils.SelfPath() == "/sbin/init" {
|
||||
if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
|
||||
// Running in init mode
|
||||
docker.SysInit()
|
||||
return
|
||||
}
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flVersion := flag.Bool("v", false, "Print version information and quit")
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
|
||||
flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
|
||||
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
|
||||
flag.Parse()
|
||||
if *flVersion {
|
||||
showVersion()
|
||||
return
|
||||
}
|
||||
if len(flHosts) > 1 {
|
||||
flHosts = flHosts[1:] //trick to display a nice defaul value in the usage
|
||||
flHosts = flHosts[1:] //trick to display a nice default value in the usage
|
||||
}
|
||||
for i, flHost := range flHosts {
|
||||
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
@@ -52,6 +58,7 @@ func main() {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GITCOMMIT = GITCOMMIT
|
||||
docker.VERSION = VERSION
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
@@ -74,6 +81,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func showVersion() {
|
||||
fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
|
||||
}
|
||||
|
||||
func createPidFile(pidfile string) error {
|
||||
if pidString, err := ioutil.ReadFile(pidfile); err == nil {
|
||||
pid, err := strconv.Atoi(string(pidString))
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
Andy Rothfusz <andy@dotcloud.com>
|
||||
Ken Cochrane <ken@dotcloud.com>
|
||||
Andy Rothfusz <andy@dotcloud.com> (@metalivedev)
|
||||
Ken Cochrane <ken@dotcloud.com> (@kencochrane)
|
||||
|
||||
+16
-13
@@ -1,14 +1,12 @@
|
||||
Docker documentation and website
|
||||
================================
|
||||
Docker Documentation
|
||||
====================
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
This is your definite place to contribute to the docker documentation. The documentation is generated from the
|
||||
.rst files under sources.
|
||||
|
||||
The folder also contains the other files to create the http://docker.io website, but you can generally ignore
|
||||
most of those.
|
||||
This is your definite place to contribute to the docker documentation. After each push to master the documentation
|
||||
is automatically generated and made available on [docs.docker.io](http://docs.docker.io)
|
||||
|
||||
Each of the .rst files under sources reflects a page on the documentation.
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -25,26 +23,22 @@ Usage
|
||||
* Change the `.rst` files with your favorite editor to your liking.
|
||||
* Run `make docs` to clean up old files and generate new ones.
|
||||
* Your static website can now be found in the `_build` directory.
|
||||
* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser.
|
||||
* To preview what you have generated run `make server` and open http://localhost:8000/ in your favorite browser.
|
||||
|
||||
Working using GitHub's file editor
|
||||
----------------------------------
|
||||
Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows
|
||||
you to preview your changes right online. Just be carefull not to create many commits.
|
||||
you to preview your changes right online. Just be careful not to create many commits.
|
||||
|
||||
Images
|
||||
------
|
||||
When you need to add images, try to make them as small as possible (e.g. as gif).
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
|
||||
So changes to those pages should be made directly in html
|
||||
* For the template the css is compiled from less. When changes are needed they can be compiled using
|
||||
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
|
||||
|
||||
|
||||
Guides on using sphinx
|
||||
----------------------
|
||||
* To make links to certain pages create a link target like so:
|
||||
@@ -75,3 +69,12 @@ Guides on using sphinx
|
||||
* Code examples
|
||||
|
||||
Start without $, so it's easy to copy and paste.
|
||||
|
||||
Manpages
|
||||
--------
|
||||
|
||||
* To make the manpages, simply run 'make man'. Please note there is a bug in spinx 1.1.3 which makes this fail.
|
||||
Upgrade to the latest version of sphinx.
|
||||
* Then preview the manpage by running `man _build/man/docker.1`, where _build/man/docker.1 is the path to the generated
|
||||
manfile
|
||||
* The manpages are also autogenerated by our hosted readthedocs here: http://docs-docker.dotcloud.com/projects/docker/downloads/
|
||||
|
||||
@@ -1 +1 @@
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Solomon Hykes <solomon@dotcloud.com> (@shykes)
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
||||
.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to
|
||||
.. document the REST API.
|
||||
|
||||
=================
|
||||
Docker Remote API
|
||||
=================
|
||||
@@ -12,36 +15,71 @@ Docker Remote API
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker deamon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push
|
||||
- By default the Docker daemon listens on unix:///var/run/docker.sock and the client must have root access to interact with the daemon
|
||||
- If a group named *docker* exists on your system, docker will apply ownership of the socket to the group
|
||||
- The API tends to be REST, but for some complex commands, like attach
|
||||
or pull, the HTTP connection is hijacked to transport stdout stdin
|
||||
and stderr
|
||||
- Since API version 1.2, the auth configuration is now handled client
|
||||
side, so the client has to send the authConfig as POST in
|
||||
/images/(name)/push
|
||||
|
||||
2. Versions
|
||||
===========
|
||||
|
||||
The current verson of the API is 1.3
|
||||
Calling /images/<name>/insert is the same as calling /v1.3/images/<name>/insert
|
||||
You can still call an old version of the api using /v1.0/images/<name>/insert
|
||||
The current version of the API is 1.4
|
||||
|
||||
:doc:`docker_remote_api_v1.3`
|
||||
Calling /images/<name>/insert is the same as calling
|
||||
/v1.4/images/<name>/insert
|
||||
|
||||
You can still call an old version of the api using
|
||||
/v1.0/images/<name>/insert
|
||||
|
||||
:doc:`docker_remote_api_v1.4`
|
||||
*****************************
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
Listing processes (/top):
|
||||
.. http:post:: /images/create
|
||||
|
||||
- List the processes inside a container
|
||||
**New!** When pull a repo, all images are now downloaded in parallel.
|
||||
|
||||
.. http:get:: /containers/(id)/top
|
||||
|
||||
**New!** You can now use ps args with docker top, like `docker top <container_id> aux`
|
||||
|
||||
.. http:get:: /events:
|
||||
|
||||
**New!** Image's name added in the events
|
||||
|
||||
:doc:`docker_remote_api_v1.3`
|
||||
*****************************
|
||||
|
||||
docker v0.5.0 51f6c4a_
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
.. http:get:: /containers/(id)/top
|
||||
|
||||
List the processes running inside a container.
|
||||
|
||||
.. http:get:: /events:
|
||||
|
||||
**New!** Monitor docker's events via streaming or via polling
|
||||
|
||||
Builder (/build):
|
||||
|
||||
- Simplify the upload of the build context
|
||||
- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
|
||||
- Simply stream a tarball instead of multipart upload with 4
|
||||
intermediary buffers
|
||||
- Simpler, less memory usage, less disk usage and faster
|
||||
|
||||
.. Note::
|
||||
The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
|
||||
.. Warning::
|
||||
|
||||
The /build improvements are not reverse-compatible. Pre 1.3 clients
|
||||
will break on /build.
|
||||
|
||||
List containers (/containers/json):
|
||||
|
||||
@@ -49,7 +87,8 @@ List containers (/containers/json):
|
||||
|
||||
Start containers (/containers/<id>/start):
|
||||
|
||||
- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls
|
||||
- You can now pass host-specific configuration (e.g. bind mounts) in
|
||||
the POST body for start calls
|
||||
|
||||
:doc:`docker_remote_api_v1.2`
|
||||
*****************************
|
||||
@@ -60,14 +99,25 @@ What's new
|
||||
----------
|
||||
|
||||
The auth configuration is now handled by the client.
|
||||
The client should send it's authConfig as POST on each call of /images/(name)/push
|
||||
|
||||
.. http:get:: /auth is now deprecated
|
||||
.. http:post:: /auth only checks the configuration but doesn't store it on the server
|
||||
The client should send it's authConfig as POST on each call of
|
||||
/images/(name)/push
|
||||
|
||||
Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
|
||||
.. http:get:: /auth
|
||||
|
||||
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
|
||||
**Deprecated.**
|
||||
|
||||
.. http:post:: /auth
|
||||
|
||||
Only checks the configuration but doesn't store it on the server
|
||||
|
||||
Deleting an image is now improved, will only untag the image if it
|
||||
has children and remove all the untagged parents if has any.
|
||||
|
||||
.. http:post:: /images/<name>/delete
|
||||
|
||||
Now returns a JSON structure with the list of images
|
||||
deleted/untagged.
|
||||
|
||||
|
||||
:doc:`docker_remote_api_v1.1`
|
||||
@@ -82,7 +132,7 @@ What's new
|
||||
.. http:post:: /images/(name)/insert
|
||||
.. http:post:: /images/(name)/push
|
||||
|
||||
Uses json stream instead of HTML hijack, it looks like this:
|
||||
Uses json stream instead of HTML hijack, it looks like this:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
@@ -109,6 +159,7 @@ Initial version
|
||||
.. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
|
||||
.. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
|
||||
.. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168
|
||||
.. _51f6c4a: https://github.com/dotcloud/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909
|
||||
|
||||
==================================
|
||||
Docker Remote API Client Libraries
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.0
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
@@ -12,7 +17,7 @@ Docker Remote API v1.0
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker deamon is 4243
|
||||
- Default port in the docker daemon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
|
||||
2. Endpoints
|
||||
@@ -300,8 +305,8 @@ Start a container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Stop a contaier
|
||||
***************
|
||||
Stop a container
|
||||
****************
|
||||
|
||||
.. http:post:: /containers/(id)/stop
|
||||
|
||||
@@ -827,7 +832,7 @@ Build an image from Dockerfile via stdin
|
||||
|
||||
{{ STREAM }}
|
||||
|
||||
:query t: tag to be applied to the resulting image in case of success
|
||||
:query t: repository name to be applied to the resulting image in case of success
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.1
|
||||
:description: API Documentation for Docker
|
||||
@@ -13,7 +17,7 @@ Docker Remote API v1.1
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker deamon is 4243
|
||||
- Default port in the docker daemon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
|
||||
2. Endpoints
|
||||
@@ -301,8 +305,8 @@ Start a container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Stop a contaier
|
||||
***************
|
||||
Stop a container
|
||||
****************
|
||||
|
||||
.. http:post:: /containers/(id)/stop
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.2
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
@@ -12,7 +17,7 @@ Docker Remote API v1.2
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker deamon is 4243
|
||||
- Default port in the docker daemon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
|
||||
2. Endpoints
|
||||
@@ -312,8 +317,8 @@ Start a container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Stop a contaier
|
||||
***************
|
||||
Stop a container
|
||||
****************
|
||||
|
||||
.. http:post:: /containers/(id)/stop
|
||||
|
||||
@@ -865,7 +870,7 @@ Build an image from Dockerfile via stdin
|
||||
|
||||
{{ STREAM }}
|
||||
|
||||
:query t: tag to be applied to the resulting image in case of success
|
||||
:query t: repository name to be applied to the resulting image in case of success
|
||||
:query remote: resource to fetch, as URI
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.3
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
@@ -12,7 +17,7 @@ Docker Remote API v1.3
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker deamon is 4243
|
||||
- Default port in the docker daemon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
|
||||
2. Endpoints
|
||||
@@ -360,8 +365,8 @@ Start a container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Stop a contaier
|
||||
***************
|
||||
Stop a container
|
||||
****************
|
||||
|
||||
.. http:post:: /containers/(id)/stop
|
||||
|
||||
@@ -920,7 +925,7 @@ Build an image from Dockerfile via stdin
|
||||
|
||||
The Content-type header should be set to "application/tar".
|
||||
|
||||
:query t: tag to be applied to the resulting image in case of success
|
||||
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
@@ -984,7 +989,10 @@ Display system-wide information
|
||||
"NFd": 11,
|
||||
"NGoroutines":21,
|
||||
"MemoryLimit":true,
|
||||
"SwapLimit":false
|
||||
"SwapLimit":false,
|
||||
"EventsListeners":"0",
|
||||
"LXCVersion":"0.7.5",
|
||||
"KernelVersion":"3.8.0-19-generic"
|
||||
}
|
||||
|
||||
:statuscode 200: no error
|
||||
@@ -1054,6 +1062,36 @@ Create a new image from a container's changes
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Monitor Docker's events
|
||||
***********************
|
||||
|
||||
.. http:get:: /events
|
||||
|
||||
Get events from docker, either in real time via streaming, or via polling (using `since`)
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /events?since=1374067924
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{"status":"create","id":"dfdf82bd3881","time":1374067924}
|
||||
{"status":"start","id":"dfdf82bd3881","time":1374067924}
|
||||
{"status":"stop","id":"dfdf82bd3881","time":1374067966}
|
||||
{"status":"destroy","id":"dfdf82bd3881","time":1374067970}
|
||||
|
||||
:query since: timestamp used for polling
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
================
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -452,7 +452,7 @@ User Register
|
||||
"username": "foobar"'}
|
||||
|
||||
:jsonparameter email: valid email address, that needs to be confirmed
|
||||
:jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
|
||||
:jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9\_].
|
||||
:jsonparameter password: min 5 characters
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
:description: Documentation for docker Registry and Registry API
|
||||
:keywords: docker, registry, api, index
|
||||
|
||||
.. _registryindexspec:
|
||||
|
||||
=====================
|
||||
Registry & index Spec
|
||||
Registry & Index Spec
|
||||
=====================
|
||||
|
||||
.. contents:: Table of Contents
|
||||
@@ -154,7 +155,7 @@ API (pulling repository foo/bar):
|
||||
|
||||
.. note::
|
||||
|
||||
**It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed.
|
||||
**It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authenticated and the security is not guaranteed.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -367,7 +368,8 @@ POST /v1/users
|
||||
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
|
||||
|
||||
**Validation**:
|
||||
- **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
|
||||
- **username**: min 4 character, max 30 characters, must match the regular
|
||||
expression [a-z0-9\_].
|
||||
- **password**: min 5 characters
|
||||
|
||||
**Valid**: return HTTP 200
|
||||
@@ -566,4 +568,4 @@ Next request::
|
||||
---------------------
|
||||
|
||||
- 1.0 : May 6th 2013 : initial release
|
||||
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
|
||||
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
|
||||
|
||||
@@ -13,9 +13,9 @@ Docker Usage
|
||||
To list available commands, either run ``docker`` with no parameters or execute
|
||||
``docker help``::
|
||||
|
||||
$ docker
|
||||
$ sudo docker
|
||||
Usage: docker [OPTIONS] COMMAND [arg...]
|
||||
-H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
|
||||
-H=[unix:///var/run/docker.sock]: tcp://host:port to bind/connect to or unix://path/to/socket to use
|
||||
|
||||
A self-sufficient runtime for linux containers.
|
||||
|
||||
@@ -30,6 +30,7 @@ Available Commands
|
||||
command/attach
|
||||
command/build
|
||||
command/commit
|
||||
command/cp
|
||||
command/diff
|
||||
command/export
|
||||
command/history
|
||||
|
||||
@@ -10,4 +10,50 @@
|
||||
|
||||
Usage: docker attach CONTAINER
|
||||
|
||||
Attach to a running container
|
||||
Attach to a running container.
|
||||
|
||||
You can detach from the container again (and leave it running) with
|
||||
``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of
|
||||
the Docker client when it quits.
|
||||
|
||||
To stop a container, use ``docker stop``
|
||||
|
||||
To kill the container, use ``docker kill``
|
||||
|
||||
Examples:
|
||||
---------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ID=$(sudo docker run -d ubuntu /usr/bin/top -b)
|
||||
$ sudo docker attach $ID
|
||||
top - 02:05:52 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
|
||||
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
|
||||
Cpu(s): 0.1%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
Mem: 373572k total, 355560k used, 18012k free, 27872k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221740k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
|
||||
|
||||
top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
|
||||
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
|
||||
Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
Mem: 373572k total, 355244k used, 18328k free, 27872k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221776k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
|
||||
|
||||
top - 02:05:58 up 3:06, 0 users, load average: 0.01, 0.02, 0.05
|
||||
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
|
||||
Cpu(s): 0.2%us, 0.3%sy, 0.0%ni, 99.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
Mem: 373572k total, 355780k used, 17792k free, 27880k buffers
|
||||
Swap: 786428k total, 0k used, 786428k free, 221776k cached
|
||||
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
|
||||
^C$
|
||||
$ sudo docker stop $ID
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
|
||||
Usage: docker build [OPTIONS] PATH | URL | -
|
||||
Build a new container image from the source code at PATH
|
||||
-t="": Tag to be applied to the resulting image in case of success.
|
||||
-t="": Repository name (and optionally a tag) to be applied to the resulting image in case of success.
|
||||
-q=false: Suppress verbose build output.
|
||||
-no-cache: Do not use the cache when building the image.
|
||||
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
|
||||
|
||||
|
||||
@@ -20,25 +21,44 @@ Examples
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build .
|
||||
sudo docker build .
|
||||
|
||||
| This will read the Dockerfile from the current directory. It will also send any other files and directories found in the current directory to the docker daemon.
|
||||
| The contents of this directory would be used by ADD commands found within the Dockerfile.
|
||||
| This will send a lot of data to the docker daemon if the current directory contains a lot of data.
|
||||
| If the absolute path is provided instead of '.', only the files and directories required by the ADD commands from the Dockerfile will be added to the context and transferred to the docker daemon.
|
||||
|
|
||||
This will read the ``Dockerfile`` from the current directory. It will
|
||||
also send any other files and directories found in the current
|
||||
directory to the ``docker`` daemon.
|
||||
|
||||
The contents of this directory would be used by ``ADD`` commands found
|
||||
within the ``Dockerfile``. This will send a lot of data to the
|
||||
``docker`` daemon if the current directory contains a lot of data. If
|
||||
the absolute path is provided instead of ``.`` then only the files and
|
||||
directories required by the ADD commands from the ``Dockerfile`` will be
|
||||
added to the context and transferred to the ``docker`` daemon.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build - < Dockerfile
|
||||
sudo docker build -t vieux/apache:2.0 .
|
||||
|
||||
| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
|
||||
| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.
|
||||
This will build like the previous example, but it will then tag the
|
||||
resulting image. The repository name will be ``vieux/apache`` and the
|
||||
tag will be ``2.0``
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build github.com/creack/docker-firefox
|
||||
sudo docker build - < Dockerfile
|
||||
|
||||
| This will clone the github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile.
|
||||
| Note that you can specify an arbitrary git repository by using the 'git://' schema.
|
||||
This will read a ``Dockerfile`` from *stdin* without context. Due to
|
||||
the lack of a context, no contents of any local directory will be sent
|
||||
to the ``docker`` daemon. ``ADD`` doesn't work when running in this
|
||||
mode because the absence of the context provides no source files to
|
||||
copy to the container.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build github.com/creack/docker-firefox
|
||||
|
||||
This will clone the Github repository and use it as context. The
|
||||
``Dockerfile`` at the root of the repository is used as
|
||||
``Dockerfile``. Note that you can specify an arbitrary git repository
|
||||
by using the ``git://`` schema.
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
-run="": Config automatically applied when the image is
|
||||
run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Full -run example::
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
:title: Cp Command
|
||||
:description: Copy files/folders from the containers filesystem to the host path
|
||||
:keywords: cp, docker, container, documentation, copy
|
||||
|
||||
===========================================================
|
||||
``cp`` -- Copy files/folders from the containers filesystem to the host path
|
||||
===========================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker cp CONTAINER:RESOURCE HOSTPATH
|
||||
|
||||
Copy files/folders from the containers filesystem to the host path. Paths are relative to the root of the filesystem.
|
||||
@@ -21,6 +21,6 @@ Displaying images visually
|
||||
|
||||
::
|
||||
|
||||
docker images -viz | dot -Tpng -o docker.png
|
||||
sudo docker images -viz | dot -Tpng -o docker.png
|
||||
|
||||
.. image:: images/docker_images.gif
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
|
||||
Create a new filesystem image from the contents of a tarball
|
||||
|
||||
At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip)
|
||||
containing a root filesystem. If you would like to import from a local directory or archive,
|
||||
you can use the ``-`` parameter to take the data from standard in.
|
||||
At this time, the URL must start with ``http`` and point to a single
|
||||
file archive (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) containing a
|
||||
root filesystem. If you would like to import from a local directory or
|
||||
archive, you can use the ``-`` parameter to take the data from
|
||||
standard in.
|
||||
|
||||
Examples
|
||||
--------
|
||||
@@ -22,19 +24,21 @@ Examples
|
||||
Import from a remote location
|
||||
.............................
|
||||
|
||||
``$ docker import http://example.com/exampleimage.tgz exampleimagerepo``
|
||||
``$ sudo docker import http://example.com/exampleimage.tgz exampleimagerepo``
|
||||
|
||||
Import from a local file
|
||||
........................
|
||||
|
||||
Import to docker via pipe and standard in
|
||||
|
||||
``$ cat exampleimage.tgz | docker import - exampleimagelocal``
|
||||
|
||||
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal``
|
||||
|
||||
Import from a local directory
|
||||
.............................
|
||||
|
||||
``$ sudo tar -c . | docker import - exampleimagedir``
|
||||
|
||||
Note the ``sudo`` in this example -- you must preserve the ownership of the files (especially root ownership)
|
||||
during the archiving with tar. If you are not root (or sudo) when you tar, then the ownerships might not get preserved.
|
||||
Note the ``sudo`` in this example -- you must preserve the ownership
|
||||
of the files (especially root ownership) during the archiving with
|
||||
tar. If you are not root (or sudo) when you tar, then the ownerships
|
||||
might not get preserved.
|
||||
|
||||
@@ -8,21 +8,77 @@
|
||||
|
||||
::
|
||||
|
||||
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||
Usage: docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
|
||||
|
||||
Run a command in a new container
|
||||
|
||||
-a=map[]: Attach to stdin, stdout or stderr.
|
||||
-c=0: CPU shares (relative weight)
|
||||
-d=false: Detached mode: leave the container running in the background
|
||||
-cidfile="": Write the container ID to the file
|
||||
-d=false: Detached mode: Run container in the background, print new container id
|
||||
-e=[]: Set environment variables
|
||||
-h="": Container host name
|
||||
-i=false: Keep stdin open even if not attached
|
||||
-privileged=false: Give extended privileges to this container
|
||||
-m=0: Memory limit (in bytes)
|
||||
-n=true: Enable networking for this container
|
||||
-p=[]: Map a network port to the container
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-u="": Username or UID
|
||||
-d=[]: Set custom dns servers for the container
|
||||
-dns=[]: Set custom dns servers for the container
|
||||
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
|
||||
-volumes-from="": Mount all volumes from the given container.
|
||||
-entrypoint="": Overwrite the default entrypoint set by the image.
|
||||
-w="": Working directory inside the container
|
||||
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
|
||||
|
||||
This will create a container and print "test" to the console. The
|
||||
``cidfile`` flag makes docker attempt to create a new file and write the
|
||||
container ID to it. If the file exists already, docker will return an
|
||||
error. Docker will close this file when docker run exits.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run mount -t tmpfs none /var/spool/squid
|
||||
|
||||
This will *not* work, because by default, most potentially dangerous
|
||||
kernel capabilities are dropped; including ``cap_sys_admin`` (which is
|
||||
required to mount filesystems). However, the ``-privileged`` flag will
|
||||
allow it to run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -privileged mount -t tmpfs none /var/spool/squid
|
||||
|
||||
The ``-privileged`` flag gives *all* capabilities to the container,
|
||||
and it also lifts all the limitations enforced by the ``device``
|
||||
cgroup controller. In other words, the container can then do almost
|
||||
everything that the host can do. This flag exists to allow special
|
||||
use-cases, like running Docker within Docker.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -w /path/to/dir/ -i -t ubuntu pwd
|
||||
|
||||
The ``-w`` lets the command beeing executed inside directory given,
|
||||
here /path/to/dir/. If the path does not exists it is created inside the
|
||||
container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
|
||||
|
||||
The ``-v`` flag mounts the current working directory into the container.
|
||||
The ``-w`` lets the command beeing executed inside the current
|
||||
working directory, by changeing into the directory to the value
|
||||
returned by ``pwd``. So this combination executes the command
|
||||
using the container, but inside the current working directory.
|
||||
|
||||
|
||||
|
||||
@@ -10,5 +10,5 @@
|
||||
|
||||
Usage: docker search TERM
|
||||
|
||||
Searches for the TERM parameter on the Docker index and prints out a list of repositories
|
||||
that match.
|
||||
Searches for the TERM parameter on the Docker index and prints out
|
||||
a list of repositories that match.
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
::
|
||||
|
||||
Usage: docker stop [OPTIONS] NAME
|
||||
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Stop a running container
|
||||
|
||||
-t=10: Number of seconds to wait for the container to stop before killing it.
|
||||
|
||||
@@ -15,6 +15,7 @@ Contents:
|
||||
attach <command/attach>
|
||||
build <command/build>
|
||||
commit <command/commit>
|
||||
cp <command/cp>
|
||||
diff <command/diff>
|
||||
export <command/export>
|
||||
history <command/history>
|
||||
@@ -37,5 +38,6 @@ Contents:
|
||||
start <command/start>
|
||||
stop <command/stop>
|
||||
tag <command/tag>
|
||||
top <command/top>
|
||||
version <command/version>
|
||||
wait <command/wait>
|
||||
wait <command/wait>
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
|
||||
.. _dockermanifesto:
|
||||
|
||||
*(This was our original Welcome page, but it is a bit forward-looking
|
||||
for docs, and maybe not enough vision for a true manifesto. We'll
|
||||
reveal more vision in the future to make it more Manifesto-y.)*
|
||||
|
||||
Docker Manifesto
|
||||
----------------
|
||||
|
||||
@@ -131,60 +127,3 @@ sitting 10 miles away.
|
||||
|
||||
With Standard Containers we can put an end to that embarrassment, by
|
||||
making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
||||
|
||||
Standard Container Specification
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
(TODO)
|
||||
|
||||
Image format
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Standard operations
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Copy
|
||||
- Run
|
||||
- Stop
|
||||
- Wait
|
||||
- Commit
|
||||
- Attach standard streams
|
||||
- List filesystem changes
|
||||
- ...
|
||||
|
||||
Execution environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Root filesystem
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Environment variables
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Process arguments
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Networking
|
||||
^^^^^^^^^^
|
||||
|
||||
Process namespacing
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Resource limits
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Process monitoring
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Logging
|
||||
^^^^^^^
|
||||
|
||||
Signals
|
||||
^^^^^^^
|
||||
|
||||
Pseudo-terminal allocation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Security
|
||||
^^^^^^^^
|
||||
|
||||
|
||||
@@ -51,9 +51,7 @@ source_suffix = '.rst'
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
#disable the parmalinks on headers, I find them really annoying
|
||||
html_add_permalinks = None
|
||||
|
||||
html_add_permalinks = u'¶'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'toctree'
|
||||
@@ -203,7 +201,7 @@ latex_elements = {
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Docker.tex', u'Docker Documentation',
|
||||
('toctree', 'Docker.tex', u'Docker Documentation',
|
||||
u'Team Docker', 'manual'),
|
||||
]
|
||||
|
||||
@@ -233,7 +231,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'docker', u'Docker Documentation',
|
||||
('toctree', 'docker', u'Docker Documentation',
|
||||
[u'Team Docker'], 1)
|
||||
]
|
||||
|
||||
@@ -247,7 +245,7 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Docker', u'Docker Documentation',
|
||||
('toctree', 'Docker', u'Docker Documentation',
|
||||
u'Team Docker', 'Docker', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:title: Contribution Guidelines
|
||||
:description: Contribution guidelines: create issues, convetions, pull requests
|
||||
:description: Contribution guidelines: create issues, conventions, pull requests
|
||||
:keywords: contributing, docker, documentation, help, guideline
|
||||
|
||||
Contributing to Docker
|
||||
|
||||
@@ -5,18 +5,23 @@
|
||||
Setting Up a Dev Environment
|
||||
============================
|
||||
|
||||
To make it easier to contribute to Docker, we provide a standard development environment. It is important that
|
||||
the same environment be used for all tests, builds and releases. The standard development environment defines
|
||||
all build dependencies: system libraries and binaries, go environment, go dependencies, etc.
|
||||
To make it easier to contribute to Docker, we provide a standard
|
||||
development environment. It is important that the same environment be
|
||||
used for all tests, builds and releases. The standard development
|
||||
environment defines all build dependencies: system libraries and
|
||||
binaries, go environment, go dependencies, etc.
|
||||
|
||||
|
||||
Step 1: install docker
|
||||
----------------------
|
||||
|
||||
Docker's build environment itself is a docker container, so the first step is to install docker on your system.
|
||||
Docker's build environment itself is a Docker container, so the first
|
||||
step is to install docker on your system.
|
||||
|
||||
You can follow the `install instructions most relevant to your system <https://docs.docker.io/en/latest/installation/>`.
|
||||
Make sure you have a working, up-to-date docker installation, then continue to the next step.
|
||||
You can follow the `install instructions most relevant to your system
|
||||
<https://docs.docker.io/en/latest/installation/>`_. Make sure you have
|
||||
a working, up-to-date docker installation, then continue to the next
|
||||
step.
|
||||
|
||||
|
||||
Step 2: check out the source
|
||||
@@ -35,22 +40,24 @@ When you are ready to build docker, run this command:
|
||||
|
||||
::
|
||||
|
||||
docker build -t docker .
|
||||
sudo docker build -t docker .
|
||||
|
||||
This will build the revision currently checked out in the repository. Feel free to check out the version
|
||||
of your choice.
|
||||
This will build the revision currently checked out in the
|
||||
repository. Feel free to check out the version of your choice.
|
||||
|
||||
If the build is successful, congratulations! You have produced a clean build of docker, neatly encapsulated
|
||||
in a standard build environment.
|
||||
If the build is successful, congratulations! You have produced a clean
|
||||
build of docker, neatly encapsulated in a standard build environment.
|
||||
|
||||
You can run an interactive session in the newly built container:
|
||||
|
||||
::
|
||||
docker run -i -t docker bash
|
||||
|
||||
sudo docker run -i -t docker bash
|
||||
|
||||
|
||||
To extract the binaries from the container:
|
||||
|
||||
::
|
||||
docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
|
||||
|
||||
sudo docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
|
||||
|
||||
|
||||
@@ -9,27 +9,29 @@ CouchDB Service
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Here's an example of using data volumes to share the same data between 2 couchdb containers.
|
||||
This could be used for hot upgrades, testing different versions of couchdb on the same data, etc.
|
||||
Here's an example of using data volumes to share the same data between
|
||||
2 CouchDB containers. This could be used for hot upgrades, testing
|
||||
different versions of CouchDB on the same data, etc.
|
||||
|
||||
Create first database
|
||||
---------------------
|
||||
|
||||
Note that we're marking /var/lib/couchdb as a data volume.
|
||||
Note that we're marking ``/var/lib/couchdb`` as a data volume.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH1=$(docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
COUCH1=$(sudo docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
|
||||
|
||||
Add data to the first database
|
||||
------------------------------
|
||||
|
||||
We're assuming your docker host is reachable at `localhost`. If not, replace `localhost` with the public IP of your docker host.
|
||||
We're assuming your docker host is reachable at `localhost`. If not,
|
||||
replace `localhost` with the public IP of your docker host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(docker port $COUCH1 5984)/_utils/"
|
||||
URL="http://$HOST:$(sudo docker port $COUCH1 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser, and use the couch interface to add data"
|
||||
|
||||
Create second database
|
||||
@@ -39,7 +41,7 @@ This time, we're requesting shared access to $COUCH1's volumes.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
COUCH2=$(docker run -d -volumes-from $COUCH1) shykes/couchdb:2013-05-03)
|
||||
COUCH2=$(sudo docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||
|
||||
Browse data on the second database
|
||||
----------------------------------
|
||||
@@ -47,7 +49,8 @@ Browse data on the second database
|
||||
.. code-block:: bash
|
||||
|
||||
HOST=localhost
|
||||
URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database!"
|
||||
URL="http://$HOST:$(sudo docker port $COUCH2 5984)/_utils/"
|
||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
|
||||
|
||||
Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
|
||||
Congratulations, you are running 2 Couchdb containers, completely
|
||||
isolated from each other *except* for their data.
|
||||
|
||||
@@ -11,26 +11,28 @@ Hello World
|
||||
|
||||
This is the most basic example available for using Docker.
|
||||
|
||||
Download the base container
|
||||
Download the base image (named "ubuntu"):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download a base image
|
||||
docker pull base
|
||||
# Download an ubuntu image
|
||||
sudo docker pull ubuntu
|
||||
|
||||
The *base* image is a minimal *ubuntu* based container, alternatively you can select *busybox*, a bare
|
||||
minimal linux system. The images are retrieved from the docker repository.
|
||||
Alternatively to the *ubuntu* image, you can select *busybox*, a bare
|
||||
minimal Linux system. The images are retrieved from the Docker
|
||||
repository.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
#run a simple echo command, that will echo hello world back to the console over standard out.
|
||||
docker run base /bin/echo hello world
|
||||
sudo docker run ubuntu /bin/echo hello world
|
||||
|
||||
**Explanation:**
|
||||
|
||||
- **"sudo"** execute the following commands as user *root*
|
||||
- **"docker run"** run a command in a new container
|
||||
- **"base"** is the image we want to run the command inside of.
|
||||
- **"ubuntu"** is the image we want to run the command inside of.
|
||||
- **"/bin/echo"** is the command we want to run in the container
|
||||
- **"hello world"** is the input for the echo command
|
||||
|
||||
@@ -47,4 +49,4 @@ See the example in action
|
||||
</div>
|
||||
|
||||
|
||||
Continue to the :ref:`hello_world_daemon` example.
|
||||
Continue to the :ref:`hello_world_daemon` example.
|
||||
|
||||
@@ -11,27 +11,35 @@ Hello World Daemon
|
||||
|
||||
The most boring daemon ever written.
|
||||
|
||||
This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
|
||||
We will use the base image to run a simple hello world daemon that will just print hello world to standard
|
||||
out every second. It will continue to do this until we stop it.
|
||||
This example assumes you have Docker installed and with the Ubuntu
|
||||
image already imported ``docker pull ubuntu``. We will use the Ubuntu
|
||||
image to run a simple hello world daemon that will just print hello
|
||||
world to standard out every second. It will continue to do this until
|
||||
we stop it.
|
||||
|
||||
**Steps:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
|
||||
CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done")
|
||||
|
||||
We are going to run a simple hello world daemon in a new container made from the base image.
|
||||
We are going to run a simple hello world daemon in a new container
|
||||
made from the *ubuntu* image.
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
|
||||
- **"base"** is the image we want to run the command inside of.
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d"
|
||||
so it runs as a daemon.
|
||||
- **"ubuntu"** is the image we want to run the command inside of.
|
||||
- **"/bin/sh -c"** is the command we want to run in the container
|
||||
- **"while true; do echo hello world; sleep 1; done"** is the mini script we want to run, that will just print hello world once a second until we stop it.
|
||||
- **$CONTAINER_ID** the output of the run command will return a container id, we can use in future commands to see what is going on with this process.
|
||||
- **"while true; do echo hello world; sleep 1; done"** is the mini
|
||||
script we want to run, that will just print hello world once a
|
||||
second until we stop it.
|
||||
- **$CONTAINER_ID** the output of the run command will return a
|
||||
container id, we can use in future commands to see what is going on
|
||||
with this process.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker logs $CONTAINER_ID
|
||||
sudo docker logs $CONTAINER_ID
|
||||
|
||||
Check the logs make sure it is working correctly.
|
||||
|
||||
@@ -40,16 +48,17 @@ Check the logs make sure it is working correctly.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker attach $CONTAINER_ID
|
||||
sudo docker attach $CONTAINER_ID
|
||||
|
||||
Attach to the container to see the results in realtime.
|
||||
|
||||
- **"docker attach**" This will allow us to attach to a background process to see what is going on.
|
||||
- **"docker attach**" This will allow us to attach to a background
|
||||
process to see what is going on.
|
||||
- **$CONTAINER_ID** The Id of the container we want to attach too.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
sudo docker ps
|
||||
|
||||
Check the process list to make sure it is running.
|
||||
|
||||
@@ -57,7 +66,7 @@ Check the process list to make sure it is running.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker stop $CONTAINER_ID
|
||||
sudo docker stop $CONTAINER_ID
|
||||
|
||||
Stop the container, since we don't need it anymore.
|
||||
|
||||
@@ -66,7 +75,7 @@ Stop the container, since we don't need it anymore.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
sudo docker ps
|
||||
|
||||
Make sure it is really stopped.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:title: Docker Examples
|
||||
:description: Examples on how to use Docker
|
||||
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples
|
||||
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql
|
||||
|
||||
|
||||
|
||||
@@ -20,3 +20,5 @@ Contents:
|
||||
running_redis_service
|
||||
running_ssh_service
|
||||
couchdb_data_volumes
|
||||
postgresql_service
|
||||
mongodb
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
:title: Building a Docker Image with MongoDB
|
||||
:description: How to build a Docker image with MongoDB pre-installed
|
||||
:keywords: docker, example, package installation, networking, mongodb
|
||||
|
||||
.. _mongodb_image:
|
||||
|
||||
Building an Image with MongoDB
|
||||
==============================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show how you can build your own
|
||||
docker images with MongoDB preinstalled. We will do that by
|
||||
constructing a Dockerfile that downloads a base image, adds an
|
||||
apt source and installs the database software on Ubuntu.
|
||||
|
||||
Creating a ``Dockerfile``
|
||||
+++++++++++++++++++++++++
|
||||
|
||||
Create an empty file called ``Dockerfile``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
touch Dockerfile
|
||||
|
||||
Next, define the parent image you want to use to build your own image on top of.
|
||||
Here, we’ll use `CentOS <https://index.docker.io/_/ubuntu/>`_ (tag: ``latest``)
|
||||
available on the `docker index`_:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu:latest
|
||||
|
||||
Since we want to be running the latest version of MongoDB we'll need to add the
|
||||
10gen repo to our apt sources list.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Add 10gen official apt source to the sources list
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
|
||||
RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
|
||||
|
||||
Then, we don't want Ubuntu to complain about init not being available so we'll
|
||||
divert /sbin/initctl to /bin/true so it thinks everything is working.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Hack for initctl not being available in Ubuntu
|
||||
RUN dpkg-divert --local --rename --add /sbin/initctl
|
||||
RUN ln -s /bin/true /sbin/initctl
|
||||
|
||||
Afterwards we'll be able to update our apt repositories and install MongoDB
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Install MongoDB
|
||||
RUN apt-get update
|
||||
RUN apt-get install mongodb-10gen
|
||||
|
||||
To run MongoDB we'll have to create the default data directory (because we want it to
|
||||
run without needing to provide a special configuration file)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Create the MongoDB data directory
|
||||
RUN mkdir -p /data/db
|
||||
|
||||
Finally, we'll expose the standard port that MongoDB runs on (27107)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
EXPOSE 27017
|
||||
|
||||
Now, lets build the image which will go through the ``Dockerfile`` we made and
|
||||
run all of the commands.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build -t <yourname>/mongodb .
|
||||
|
||||
Now you should be able to run ``mongod`` as a daemon and be able to connect on
|
||||
the local port!
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Regular style
|
||||
MONGO_ID=$(docker run -d <yourname>/mongodb mongod)
|
||||
|
||||
# Lean and mean
|
||||
MONGO_ID=$(docker run -d <yourname>/mongodb mongod --noprealloc --smallfiles)
|
||||
|
||||
# Check the logs out
|
||||
docker logs $MONGO_ID
|
||||
|
||||
# Connect and play around
|
||||
mongo --port <port you get from `docker ps`>
|
||||
|
||||
Sweet!
|
||||
@@ -9,10 +9,11 @@ Node.js Web App
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show you how you can build your own docker images
|
||||
from a parent image using a ``Dockerfile`` . We will do that by making a simple
|
||||
Node.js hello world web application running on CentOS. You can get the full
|
||||
source code at https://github.com/gasi/docker-node-hello.
|
||||
The goal of this example is to show you how you can build your own
|
||||
docker images from a parent image using a ``Dockerfile`` . We will do
|
||||
that by making a simple Node.js hello world web application running on
|
||||
CentOS. You can get the full source code at
|
||||
https://github.com/gasi/docker-node-hello.
|
||||
|
||||
Create Node.js app
|
||||
++++++++++++++++++
|
||||
@@ -109,16 +110,17 @@ Install your app dependencies using npm:
|
||||
# Install app dependencies
|
||||
RUN cd /src; npm install
|
||||
|
||||
Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` command to have it
|
||||
mapped by the docker daemon:
|
||||
Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` command
|
||||
to have it mapped by the docker daemon:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
Last but not least, define the command to run your app using ``CMD`` which
|
||||
defines your runtime, i.e. ``node``, and the path to our app, i.e.
|
||||
``src/index.js`` (see the step where we added the source to the container):
|
||||
Last but not least, define the command to run your app using ``CMD``
|
||||
which defines your runtime, i.e. ``node``, and the path to our app,
|
||||
i.e. ``src/index.js`` (see the step where we added the source to the
|
||||
container):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -149,19 +151,20 @@ Your ``Dockerfile`` should now look like this:
|
||||
Building your image
|
||||
+++++++++++++++++++
|
||||
|
||||
Go to the directory that has your ``Dockerfile`` and run the following command
|
||||
to build a docker image. The ``-t`` flag let’s you tag your image so it’s easier
|
||||
to find later using the ``docker images`` command:
|
||||
Go to the directory that has your ``Dockerfile`` and run the following
|
||||
command to build a docker image. The ``-t`` flag let’s you tag your
|
||||
image so it’s easier to find later using the ``docker images``
|
||||
command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker build -t <your username>/centos-node-hello .
|
||||
sudo docker build -t <your username>/centos-node-hello .
|
||||
|
||||
Your image will now be listed by docker:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker images
|
||||
sudo docker images
|
||||
|
||||
> # Example
|
||||
> REPOSITORY TAG ID CREATED
|
||||
@@ -177,17 +180,17 @@ container running in the background. Run the image you previously built:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d <your username>/centos-node-hello
|
||||
sudo docker run -d <your username>/centos-node-hello
|
||||
|
||||
Print the output of your app:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Get container ID
|
||||
docker ps
|
||||
sudo docker ps
|
||||
|
||||
# Print app output
|
||||
docker logs <container id>
|
||||
sudo docker logs <container id>
|
||||
|
||||
> # Example
|
||||
> Running on http://localhost:8080
|
||||
@@ -225,8 +228,8 @@ Now you can call your app using ``curl`` (install if needed via:
|
||||
>
|
||||
> Hello World
|
||||
|
||||
We hope this tutorial helped you get up and running with Node.js and CentOS on
|
||||
docker. You can get the full source code at
|
||||
We hope this tutorial helped you get up and running with Node.js and
|
||||
CentOS on docker. You can get the full source code at
|
||||
https://github.com/gasi/docker-node-hello.
|
||||
|
||||
Continue to :ref:`running_redis_service`.
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
:title: PostgreSQL service How-To
|
||||
:description: Running and installing a PostgreSQL service
|
||||
:keywords: docker, example, package installation, postgresql
|
||||
|
||||
.. _postgresql_service:
|
||||
|
||||
PostgreSQL Service
|
||||
==================
|
||||
|
||||
.. note::
|
||||
|
||||
A shorter version of `this blog post`_.
|
||||
|
||||
.. note::
|
||||
|
||||
As of version 0.5.2, docker requires root privileges to run.
|
||||
You have to either manually adjust your system configuration (permissions on
|
||||
/var/run/docker.sock or sudo config), or prefix `docker` with `sudo`. Check
|
||||
`this thread`_ for details.
|
||||
|
||||
.. _this blog post: http://zaiste.net/2013/08/docker_postgresql_how_to/
|
||||
.. _this thread: https://groups.google.com/forum/?fromgroups#!topic/docker-club/P3xDLqmLp0E
|
||||
|
||||
Installing PostgreSQL on Docker
|
||||
-------------------------------
|
||||
|
||||
For clarity I won't be showing commands output.
|
||||
|
||||
|
||||
Run an interactive shell in Docker container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
Update its dependencies.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
apt-get update
|
||||
|
||||
Install ``python-software-properies``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
apt-get install python-software-properties
|
||||
apt-get install software-properties-common
|
||||
|
||||
Add Pitti's PostgreSQL repository. It contains the most recent stable release
|
||||
of PostgreSQL i.e. ``9.2``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
add-apt-repository ppa:pitti/postgresql
|
||||
apt-get update
|
||||
|
||||
Finally, install PostgreSQL 9.2
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
apt-get -y install postgresql-9.2 postgresql-client-9.2 postgresql-contrib-9.2
|
||||
|
||||
Now, create a PostgreSQL superuser role that can create databases and
|
||||
other roles. Following Vagrant's convention the role will be named
|
||||
`docker` with `docker` password assigned to it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo -u postgres createuser -P -d -r -s docker
|
||||
|
||||
Create a test database also named ``docker`` owned by previously created ``docker``
|
||||
role.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo -u postgres createdb -O docker docker
|
||||
|
||||
Adjust PostgreSQL configuration so that remote connections to the
|
||||
database are possible. Make sure that inside
|
||||
``/etc/postgresql/9.2/main/pg_hba.conf`` you have following line:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
host all all 0.0.0.0/0 md5
|
||||
|
||||
Additionaly, inside ``/etc/postgresql/9.2/main/postgresql.conf``
|
||||
uncomment ``listen_addresses`` so it is as follows:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
listen_addresses='*'
|
||||
|
||||
*Note:* this PostgreSQL setup is for development only purposes. Refer
|
||||
to PostgreSQL documentation how to fine-tune these settings so that it
|
||||
is enough secure.
|
||||
|
||||
Create an image and assign it a name. ``<container_id>`` is in the
|
||||
Bash prompt; you can also locate it using ``docker ps -a``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker commit <container_id> <your username>/postgresql
|
||||
|
||||
Finally, run PostgreSQL server via ``docker``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
CONTAINER=$(sudo docker run -d -p 5432 \
|
||||
-t <your username>/postgresql \
|
||||
/bin/su postgres -c '/usr/lib/postgresql/9.2/bin/postgres \
|
||||
-D /var/lib/postgresql/9.2/main \
|
||||
-c config_file=/etc/postgresql/9.2/main/postgresql.conf')
|
||||
|
||||
Connect the PostgreSQL server using ``psql``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
CONTAINER_IP=$(sudo docker inspect $CONTAINER | grep IPAddress | awk '{ print $2 }' | tr -d ',"')
|
||||
psql -h $CONTAINER_IP -p 5432 -d docker -U docker -W
|
||||
|
||||
As before, create roles or databases if needed.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
psql (9.2.4)
|
||||
Type "help" for help.
|
||||
|
||||
docker=# CREATE DATABASE foo OWNER=docker;
|
||||
CREATE DATABASE
|
||||
|
||||
Additionally, publish there your newly created image on Docker Index.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker login
|
||||
Username: <your username>
|
||||
[...]
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker push <your username>/postgresql
|
||||
|
||||
PostgreSQL service auto-launch
|
||||
------------------------------
|
||||
|
||||
Running our image seems complicated. We have to specify the whole command with
|
||||
``docker run``. Let's simplify it so the service starts automatically when the
|
||||
container starts.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker commit <container_id> <your username>/postgresql -run='{"Cmd": \
|
||||
["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.2/bin/postgres -D \
|
||||
/var/lib/postgresql/9.2/main -c \
|
||||
config_file=/etc/postgresql/9.2/main/postgresql.conf"], PortSpecs": ["5432"]}
|
||||
|
||||
From now on, just type ``docker run <your username>/postgresql`` and
|
||||
PostgreSQL should automatically start.
|
||||
@@ -9,13 +9,16 @@ Python Web App
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
|
||||
The goal of this example is to show you how you can author your own
|
||||
docker images using a parent image, making changes to it, and then
|
||||
saving the results as a new image. We will do that by making a simple
|
||||
hello flask web application image.
|
||||
|
||||
**Steps:**
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker pull shykes/pybuilder
|
||||
sudo docker pull shykes/pybuilder
|
||||
|
||||
We are downloading the "shykes/pybuilder" docker image
|
||||
|
||||
@@ -27,46 +30,66 @@ We set a URL variable that points to a tarball of a simple helloflask web app
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
BUILD_JOB=$(docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
|
||||
BUILD_JOB=$(sudo docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
|
||||
|
||||
Inside of the "shykes/pybuilder" image there is a command called buildapp, we are running that command and passing the $URL variable from step 2 to it, and running the whole thing inside of a new container. BUILD_JOB will be set with the new container_id.
|
||||
Inside of the "shykes/pybuilder" image there is a command called
|
||||
buildapp, we are running that command and passing the $URL variable
|
||||
from step 2 to it, and running the whole thing inside of a new
|
||||
container. BUILD_JOB will be set with the new container_id.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker attach $BUILD_JOB
|
||||
sudo docker attach $BUILD_JOB
|
||||
[...]
|
||||
|
||||
We attach to the new container to see what is going on. Ctrl-C to disconnect
|
||||
While this container is running, we can attach to the new container to
|
||||
see what is going on. Ctrl-C to disconnect.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
|
||||
|
||||
Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name.
|
||||
sudo docker ps -a
|
||||
|
||||
List all docker containers. If this container has already finished
|
||||
running, it will still be listed here.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
|
||||
BUILD_IMG=$(sudo docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
|
||||
- **"-p 5000"** the web app is going to listen on this port, so it must be mapped from the container to the host system.
|
||||
Save the changes we just made in the container to a new image called
|
||||
``_/builds/github.com/hykes/helloflask/master`` and save the image id in
|
||||
the BUILD_IMG variable name.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_WORKER=$(sudo docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d"
|
||||
so it runs as a daemon.
|
||||
- **"-p 5000"** the web app is going to listen on this port, so it
|
||||
must be mapped from the container to the host system.
|
||||
- **"$BUILD_IMG"** is the image we want to run the command inside of.
|
||||
- **/usr/local/bin/runapp** is the command which starts the web app.
|
||||
|
||||
Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
|
||||
Use the new image we just created and create a new container with
|
||||
network port 5000, and return the container id and store in the
|
||||
WEB_WORKER variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker logs $WEB_WORKER
|
||||
sudo docker logs $WEB_WORKER
|
||||
* Running on http://0.0.0.0:5000/
|
||||
|
||||
view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
|
||||
View the logs for the new container using the WEB_WORKER variable, and
|
||||
if everything worked as planned you should see the line "Running on
|
||||
http://0.0.0.0:5000/" in the log output.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_PORT=$(docker port $WEB_WORKER 5000)
|
||||
|
||||
lookup the public-facing port which is NAT-ed store the private port used by the container and store it inside of the WEB_PORT variable.
|
||||
Look up the public-facing port which is NAT-ed. Find the private port
|
||||
used by the container and store it inside of the WEB_PORT variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -74,7 +97,8 @@ lookup the public-facing port which is NAT-ed store the private port used by the
|
||||
curl http://127.0.0.1:$WEB_PORT
|
||||
Hello world!
|
||||
|
||||
access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
|
||||
Access the web app using curl. If everything worked as planned you
|
||||
should see the line "Hello world!" inside of your console.
|
||||
|
||||
**Video:**
|
||||
|
||||
|
||||
@@ -7,16 +7,17 @@
|
||||
Running the Examples
|
||||
--------------------
|
||||
|
||||
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
|
||||
All the examples assume your machine is running the docker daemon. To
|
||||
run the docker daemon in the background, simply type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker -d &
|
||||
|
||||
Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client
|
||||
can run from any account.
|
||||
Now you can run docker in client mode: by defalt all commands will be
|
||||
forwarded to the ``docker`` daemon via a protected Unix socket, so you
|
||||
must run as root.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# now you can run docker commands from any account.
|
||||
docker help
|
||||
sudo docker help
|
||||
|
||||
@@ -16,12 +16,13 @@ Open a docker container
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -i -t base /bin/bash
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
Building your image
|
||||
-------------------
|
||||
|
||||
Update your docker container, install the redis server. Once installed, exit out of docker.
|
||||
Update your Docker container, install the Redis server. Once
|
||||
installed, exit out of the Docker container.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -45,7 +46,7 @@ container running in the background. Use your snapshot.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d -p 6379 <your username>/redis /usr/bin/redis-server
|
||||
sudo docker run -d -p 6379 <your username>/redis /usr/bin/redis-server
|
||||
|
||||
Test 1
|
||||
++++++
|
||||
@@ -54,8 +55,8 @@ Connect to the container with the redis-cli.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps # grab the new container id
|
||||
docker inspect <container_id> # grab the ipaddress of the container
|
||||
sudo docker ps # grab the new container id
|
||||
sudo docker inspect <container_id> # grab the ipaddress of the container
|
||||
redis-cli -h <ipaddress> -p 6379
|
||||
redis 10.0.3.32:6379> set docker awesome
|
||||
OK
|
||||
@@ -70,8 +71,8 @@ Connect to the host os with the redis-cli.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps # grab the new container id
|
||||
docker port <container_id> 6379 # grab the external port
|
||||
sudo docker ps # grab the new container id
|
||||
sudo docker port <container_id> 6379 # grab the external port
|
||||
ip addr show # grab the host ip address
|
||||
redis-cli -h <host ipaddress> -p <external port>
|
||||
redis 192.168.0.1:49153> set docker awesome
|
||||
|
||||
@@ -12,19 +12,27 @@ SSH Daemon Service
|
||||
|
||||
**Video:**
|
||||
|
||||
I've create a little screencast to show how to create a sshd service and connect to it. It is something like 11
|
||||
minutes and not entirely smooth, but gives you a good idea.
|
||||
I've create a little screencast to show how to create a sshd service
|
||||
and connect to it. It is something like 11 minutes and not entirely
|
||||
smooth, but gives you a good idea.
|
||||
|
||||
.. note::
|
||||
This screencast was created before ``docker`` version 0.5.2, so the
|
||||
daemon is unprotected and available via a TCP port. When you run
|
||||
through the same steps in a newer version of ``docker``, you will
|
||||
need to add ``sudo`` in front of each ``docker`` command in order
|
||||
to reach the daemon over its protected Unix socket.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="margin-top:10px;">
|
||||
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
You can also get this sshd container by using
|
||||
::
|
||||
|
||||
docker pull dhrp/sshd
|
||||
sudo docker pull dhrp/sshd
|
||||
|
||||
|
||||
The password is 'screencast'
|
||||
@@ -33,47 +41,57 @@ The password is 'screencast'
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Hello! We are going to try and install openssh on a container and run it as a servic
|
||||
# let's pull base to get a base ubuntu image.
|
||||
$ docker pull base
|
||||
# I had it so it was quick
|
||||
# now let's connect using -i for interactive and with -t for terminal
|
||||
# we execute /bin/bash to get a prompt.
|
||||
$ docker run -i -t base /bin/bash
|
||||
# now let's commit it
|
||||
# which container was it?
|
||||
$ docker ps -a |more
|
||||
$ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd
|
||||
# I gave the name dhrp/sshd for the container
|
||||
# now we can run it again
|
||||
$ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode
|
||||
# is it running?
|
||||
$ docker ps
|
||||
# yes!
|
||||
# let's stop it
|
||||
$ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
|
||||
$ docker ps
|
||||
# and reconnect, but now open a port to it
|
||||
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
|
||||
$ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
|
||||
# it has now given us a port to connect to
|
||||
# we have to connect using a public ip of our host
|
||||
$ hostname
|
||||
# *ifconfig* is deprecated, better use *ip addr show* now
|
||||
$ ifconfig
|
||||
$ ssh root@192.168.33.10 -p 49153
|
||||
# Ah! forgot to set root passwd
|
||||
$ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd
|
||||
$ docker ps -a
|
||||
$ docker run -i -t dhrp/sshd /bin/bash
|
||||
$ passwd
|
||||
$ exit
|
||||
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
|
||||
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
|
||||
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
|
||||
# *ifconfig* is deprecated, better use *ip addr show* now
|
||||
$ ifconfig
|
||||
$ ssh root@192.168.33.10 -p 49154
|
||||
# Thanks for watching, Thatcher thatcher@dotcloud.com
|
||||
# Hello! We are going to try and install openssh on a container and run it as a service
|
||||
# let's pull ubuntu to get a base ubuntu image.
|
||||
$ docker pull ubuntu
|
||||
# I had it so it was quick
|
||||
# now let's connect using -i for interactive and with -t for terminal
|
||||
# we execute /bin/bash to get a prompt.
|
||||
$ docker run -i -t base /bin/bash
|
||||
# yes! we are in!
|
||||
# now lets install openssh
|
||||
$ apt-get update
|
||||
$ apt-get install openssh-server
|
||||
# ok. lets see if we can run it.
|
||||
$ which sshd
|
||||
# we need to create privilege separation directory
|
||||
$ mkdir /var/run/sshd
|
||||
$ /usr/sbin/sshd
|
||||
$ exit
|
||||
# now let's commit it
|
||||
# which container was it?
|
||||
$ docker ps -a |more
|
||||
$ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd
|
||||
# I gave the name dhrp/sshd for the container
|
||||
# now we can run it again
|
||||
$ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode
|
||||
# is it running?
|
||||
$ docker ps
|
||||
# yes!
|
||||
# let's stop it
|
||||
$ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
|
||||
$ docker ps
|
||||
# and reconnect, but now open a port to it
|
||||
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
|
||||
$ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
|
||||
# it has now given us a port to connect to
|
||||
# we have to connect using a public ip of our host
|
||||
$ hostname
|
||||
# *ifconfig* is deprecated, better use *ip addr show* now
|
||||
$ ifconfig
|
||||
$ ssh root@192.168.33.10 -p 49153
|
||||
# Ah! forgot to set root passwd
|
||||
$ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd
|
||||
$ docker ps -a
|
||||
$ docker run -i -t dhrp/sshd /bin/bash
|
||||
$ passwd
|
||||
$ exit
|
||||
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
|
||||
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
|
||||
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
|
||||
# *ifconfig* is deprecated, better use *ip addr show* now
|
||||
$ ifconfig
|
||||
$ ssh root@192.168.33.10 -p 49154
|
||||
# Thanks for watching, Thatcher thatcher@dotcloud.com
|
||||
|
||||
|
||||
|
||||
+113
-13
@@ -9,45 +9,145 @@ FAQ
|
||||
Most frequently asked questions.
|
||||
--------------------------------
|
||||
|
||||
1. **How much does Docker cost?**
|
||||
How much does Docker cost?
|
||||
..........................
|
||||
|
||||
Docker is 100% free, it is open source, so you can use it without paying.
|
||||
|
||||
2. **What open source license are you using?**
|
||||
What open source license are you using?
|
||||
.......................................
|
||||
|
||||
We are using the Apache License Version 2.0, see it here: https://github.com/dotcloud/docker/blob/master/LICENSE
|
||||
We are using the Apache License Version 2.0, see it here:
|
||||
https://github.com/dotcloud/docker/blob/master/LICENSE
|
||||
|
||||
3. **Does Docker run on Mac OS X or Windows?**
|
||||
Does Docker run on Mac OS X or Windows?
|
||||
.......................................
|
||||
|
||||
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a
|
||||
virtual machine on your box, and get the best of both worlds. Check out the :ref:`install_using_vagrant` and :ref:`windows` installation guides.
|
||||
Not at this time, Docker currently only runs on Linux, but you can
|
||||
use VirtualBox to run Docker in a virtual machine on your box, and
|
||||
get the best of both worlds. Check out the
|
||||
:ref:`install_using_vagrant` and :ref:`windows` installation
|
||||
guides.
|
||||
|
||||
4. **How do containers compare to virtual machines?**
|
||||
How do containers compare to virtual machines?
|
||||
..............................................
|
||||
|
||||
They are complementary. VMs are best used to allocate chunks of hardware resources. Containers operate at the process level, which makes them very lightweight and perfect as a unit of software delivery.
|
||||
They are complementary. VMs are best used to allocate chunks of
|
||||
hardware resources. Containers operate at the process level, which
|
||||
makes them very lightweight and perfect as a unit of software
|
||||
delivery.
|
||||
|
||||
5. **Can I help by adding some questions and answers?**
|
||||
What does Docker add to just plain LXC?
|
||||
.......................................
|
||||
|
||||
Docker is not a replacement for LXC. "LXC" refers to capabilities
|
||||
of the Linux kernel (specifically namespaces and control groups)
|
||||
which allow sandboxing processes from one another, and controlling
|
||||
their resource allocations. On top of this low-level foundation of
|
||||
kernel features, Docker offers a high-level tool with several
|
||||
powerful functionalities:
|
||||
|
||||
* *Portable deployment across machines.*
|
||||
Docker defines a format for bundling an application and all its
|
||||
dependencies into a single object which can be transferred to
|
||||
any Docker-enabled machine, and executed there with the
|
||||
guarantee that the execution environment exposed to the
|
||||
application will be the same. LXC implements process sandboxing,
|
||||
which is an important pre-requisite for portable deployment, but
|
||||
that alone is not enough for portable deployment. If you sent me
|
||||
a copy of your application installed in a custom LXC
|
||||
configuration, it would almost certainly not run on my machine
|
||||
the way it does on yours, because it is tied to your machine's
|
||||
specific configuration: networking, storage, logging, distro,
|
||||
etc. Docker defines an abstraction for these machine-specific
|
||||
settings, so that the exact same Docker container can run -
|
||||
unchanged - on many different machines, with many different
|
||||
configurations.
|
||||
|
||||
* *Application-centric.*
|
||||
Docker is optimized for the deployment of applications, as
|
||||
opposed to machines. This is reflected in its API, user
|
||||
interface, design philosophy and documentation. By contrast, the
|
||||
``lxc`` helper scripts focus on containers as lightweight
|
||||
machines - basically servers that boot faster and need less
|
||||
RAM. We think there's more to containers than just that.
|
||||
|
||||
* *Automatic build.*
|
||||
Docker includes :ref:`a tool for developers to automatically
|
||||
assemble a container from their source code <dockerbuilder>`,
|
||||
with full control over application dependencies, build tools,
|
||||
packaging etc. They are free to use ``make, maven, chef, puppet,
|
||||
salt,`` Debian packages, RPMs, source tarballs, or any
|
||||
combination of the above, regardless of the configuration of the
|
||||
machines.
|
||||
|
||||
* *Versioning.*
|
||||
Docker includes git-like capabilities for tracking successive
|
||||
versions of a container, inspecting the diff between versions,
|
||||
committing new versions, rolling back etc. The history also
|
||||
includes how a container was assembled and by whom, so you get
|
||||
full traceability from the production server all the way back to
|
||||
the upstream developer. Docker also implements incremental
|
||||
uploads and downloads, similar to ``git pull``, so new versions
|
||||
of a container can be transferred by only sending diffs.
|
||||
|
||||
* *Component re-use.*
|
||||
Any container can be used as a :ref:`"base image"
|
||||
<base_image_def>` to create more specialized components. This
|
||||
can be done manually or as part of an automated build. For
|
||||
example you can prepare the ideal Python environment, and use it
|
||||
as a base for 10 different applications. Your ideal Postgresql
|
||||
setup can be re-used for all your future projects. And so on.
|
||||
|
||||
* *Sharing.*
|
||||
Docker has access to a `public registry
|
||||
<http://index.docker.io>`_ where thousands of people have
|
||||
uploaded useful containers: anything from Redis, CouchDB,
|
||||
Postgres to IRC bouncers to Rails app servers to Hadoop to base
|
||||
images for various Linux distros. The :ref:`registry
|
||||
<registryindexspec>` also includes an official "standard
|
||||
library" of useful containers maintained by the Docker team. The
|
||||
registry itself is open-source, so anyone can deploy their own
|
||||
registry to store and transfer private containers, for internal
|
||||
server deployments for example.
|
||||
|
||||
* *Tool ecosystem.*
|
||||
Docker defines an API for automating and customizing the
|
||||
creation and deployment of containers. There are a huge number
|
||||
of tools integrating with Docker to extend its
|
||||
capabilities. PaaS-like deployment (Dokku, Deis, Flynn),
|
||||
multi-node orchestration (Maestro, Salt, Mesos, Openstack Nova),
|
||||
management dashboards (docker-ui, Openstack Horizon, Shipyard),
|
||||
configuration management (Chef, Puppet), continuous integration
|
||||
(Jenkins, Strider, Travis), etc. Docker is rapidly establishing
|
||||
itself as the standard for container-based tooling.
|
||||
|
||||
Can I help by adding some questions and answers?
|
||||
................................................
|
||||
|
||||
Definitely! You can fork `the repo`_ and edit the documentation sources.
|
||||
|
||||
|
||||
42. **Where can I find more answers?**
|
||||
Where can I find more answers?
|
||||
..............................
|
||||
|
||||
You can find more answers on:
|
||||
|
||||
* `Docker club mailinglist`_
|
||||
* `Docker user mailinglist`_
|
||||
* `Docker developer mailinglist`_
|
||||
* `IRC, docker on freenode`_
|
||||
* `Github`_
|
||||
* `Ask questions on Stackoverflow`_
|
||||
* `Join the conversation on Twitter`_
|
||||
|
||||
|
||||
.. _Docker club mailinglist: https://groups.google.com/d/forum/docker-club
|
||||
.. _Docker user mailinglist: https://groups.google.com/d/forum/docker-user
|
||||
.. _Docker developer mailinglist: https://groups.google.com/d/forum/docker-dev
|
||||
.. _the repo: http://www.github.com/dotcloud/docker
|
||||
.. _IRC, docker on freenode: irc://chat.freenode.net#docker
|
||||
.. _Github: http://www.github.com/dotcloud/docker
|
||||
.. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
|
||||
.. _Join the conversation on Twitter: http://twitter.com/getdocker
|
||||
.. _Join the conversation on Twitter: http://twitter.com/docker
|
||||
|
||||
|
||||
Looking for something else to read? Checkout the :ref:`hello_world` example.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
:description: An overview of the Docker Documentation
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
.. _introduction:
|
||||
|
||||
Welcome
|
||||
=======
|
||||
|
||||
@@ -25,7 +23,7 @@ dependencies.
|
||||
commit``).
|
||||
|
||||
Each use of ``docker`` is documented here. The features of Docker are
|
||||
currently in active development, so this documention will change
|
||||
currently in active development, so this documentation will change
|
||||
frequently.
|
||||
|
||||
For an overview of Docker, please see the `Introduction
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
:title: Installation on Amazon EC2
|
||||
:title: Installation on Amazon EC2
|
||||
:description: Docker installation on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required.
|
||||
:keywords: amazon ec2, virtualization, cloud, docker, documentation, installation
|
||||
|
||||
Amazon EC2
|
||||
==========
|
||||
Using Vagrant (Amazon EC2)
|
||||
==========================
|
||||
|
||||
This page explains how to setup and run an Amazon EC2 instance from your local machine.
|
||||
Vagrant is not necessary to run Docker on EC2. You can follow the :ref:`ubuntu_linux` instructions
|
||||
installing Docker on any EC2 instance running Ubuntu
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the
|
||||
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
|
||||
|
||||
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
@@ -86,7 +90,7 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker
|
||||
sudo docker
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
Continue with the :ref:`hello_world` example.
|
||||
|
||||
@@ -56,10 +56,10 @@ Run your first container!
|
||||
.. code-block:: bash
|
||||
|
||||
# check your docker version
|
||||
./docker version
|
||||
sudo ./docker version
|
||||
|
||||
# run a container and open an interactive shell in the container
|
||||
./docker run -i -t ubuntu /bin/bash
|
||||
sudo ./docker run -i -t ubuntu /bin/bash
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,12 +15,11 @@ In short, Docker has the following kernel requirements:
|
||||
|
||||
- Cgroups and namespaces must be enabled.
|
||||
|
||||
|
||||
The officially supported kernel is the one recommended by the
|
||||
:ref:`ubuntu_linux` installation path. It is the one that most developers
|
||||
will use, and the one that receives the most attention from the core
|
||||
contributors. If you decide to go with a different kernel and hit a bug,
|
||||
please try to reproduce it with the official kernels first.
|
||||
The officially supported kernel is the one recommended by the
|
||||
:ref:`ubuntu_linux` installation path. It is the one that most developers
|
||||
will use, and the one that receives the most attention from the core
|
||||
contributors. If you decide to go with a different kernel and hit a bug,
|
||||
please try to reproduce it with the official kernels first.
|
||||
|
||||
If you cannot or do not want to use the "official" kernels,
|
||||
here is some technical background about the features (both optional and
|
||||
|
||||
@@ -10,7 +10,7 @@ Rackspace Cloud
|
||||
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
|
||||
|
||||
|
||||
Installing Docker on Ubuntu proviced by Rackspace is pretty straightforward, and you should mostly be able to follow the
|
||||
Installing Docker on Ubuntu provided by Rackspace is pretty straightforward, and you should mostly be able to follow the
|
||||
:ref:`ubuntu_linux` installation guide.
|
||||
|
||||
**However, there is one caveat:**
|
||||
|
||||
@@ -19,6 +19,8 @@ Docker has the following dependencies
|
||||
* Linux kernel 3.8 (read more about :ref:`kernel`)
|
||||
* AUFS file system support (we are working on BTRFS support as an alternative)
|
||||
|
||||
Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated Firewall) <https://help.ubuntu.com/community/UFW>`_
|
||||
|
||||
.. _ubuntu_precise:
|
||||
|
||||
Ubuntu Precise 12.04 (LTS) (64-bit)
|
||||
@@ -32,13 +34,20 @@ Dependencies
|
||||
|
||||
**Linux kernel 3.8**
|
||||
|
||||
Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in.
|
||||
Due to a bug in LXC, docker works best on the 3.8 kernel. Precise
|
||||
comes with a 3.2 kernel, so we need to upgrade it. The kernel we
|
||||
install comes with AUFS built in. We also include the generic headers
|
||||
to enable packages that depend on them, like ZFS and the VirtualBox
|
||||
guest additions. If you didn't install the headers for your "precise"
|
||||
kernel, then you can skip these headers for the "raring" kernel. But
|
||||
it is safer to include them if you're not sure.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# install the backported kernel
|
||||
sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring
|
||||
sudo apt-get update
|
||||
sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring
|
||||
|
||||
# reboot
|
||||
sudo reboot
|
||||
@@ -67,7 +76,7 @@ Verify it worked
|
||||
.. code-block:: bash
|
||||
|
||||
# download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
|
||||
docker run -i -t ubuntu /bin/bash
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
# type 'exit' to exit
|
||||
|
||||
@@ -129,9 +138,41 @@ Verify it worked
|
||||
.. code-block:: bash
|
||||
|
||||
# download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
|
||||
docker run -i -t ubuntu /bin/bash
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
# type exit to exit
|
||||
|
||||
|
||||
**Done!**, now continue with the :ref:`hello_world` example.
|
||||
|
||||
|
||||
.. _ufw:
|
||||
|
||||
Docker and UFW
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Docker uses a bridge to manage containers networking, by default UFW drop all `forwarding`, a first step is to enable forwarding:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo nano /etc/default/ufw
|
||||
----
|
||||
# Change:
|
||||
# DEFAULT_FORWARD_POLICY="DROP"
|
||||
# to
|
||||
DEFAULT_FORWARD_POLICY="ACCEPT"
|
||||
|
||||
Then reload UFW:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo ufw reload
|
||||
|
||||
|
||||
UFW's default set of rules denied all `incoming`, so if you want to be able to reach your containers from another host,
|
||||
you should allow incoming connections on the docker port (default 4243):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo ufw allow 4243/tcp
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Now you are in the VM, run docker
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker
|
||||
sudo docker
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
|
||||
@@ -14,7 +14,7 @@ switches the whole rootfs volume to read-write mode.
|
||||
Layer
|
||||
.....
|
||||
|
||||
When Docker mounts the rootfs, it starts read-only, as in a tradtional
|
||||
When Docker mounts the rootfs, it starts read-only, as in a traditional
|
||||
Linux boot, but then, instead of changing the file system to
|
||||
read-write mode, it takes advantage of a `union mount
|
||||
<http://en.wikipedia.org/wiki/Union_mount>`_ to add a read-write file
|
||||
|
||||
+84
-39
@@ -9,11 +9,13 @@ The Basics
|
||||
Starting Docker
|
||||
---------------
|
||||
|
||||
If you have used one of the quick install paths', Docker may have been installed with upstart, Ubuntu's
|
||||
system for starting processes at boot time. You should be able to run ``docker help`` and get output.
|
||||
If you have used one of the quick install paths', Docker may have been
|
||||
installed with upstart, Ubuntu's system for starting processes at boot
|
||||
time. You should be able to run ``sudo docker help`` and get output.
|
||||
|
||||
If you get ``docker: command not found`` or something like ``/var/lib/docker/repositories: permission denied``
|
||||
you will need to specify the path to it and manually start it.
|
||||
If you get ``docker: command not found`` or something like
|
||||
``/var/lib/docker/repositories: permission denied`` you will need to
|
||||
specify the path to it and manually start it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -26,46 +28,87 @@ Running an interactive shell
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download a base image
|
||||
docker pull base
|
||||
# Download an ubuntu image
|
||||
sudo docker pull ubuntu
|
||||
|
||||
# Run an interactive shell in the base image,
|
||||
# Run an interactive shell in the ubuntu image,
|
||||
# allocate a tty, attach stdin and stdout
|
||||
docker run -i -t base /bin/bash
|
||||
sudo docker run -i -t ubuntu /bin/bash
|
||||
|
||||
Bind Docker to another host/port or a unix socket
|
||||
Why ``sudo``?
|
||||
-------------
|
||||
|
||||
The ``docker`` daemon always runs as root, and since ``docker``
|
||||
version 0.5.2, ``docker`` binds to a Unix socket instead of a TCP
|
||||
port. By default that Unix socket is owned by the user *root*, and so,
|
||||
by default, you can access it with ``sudo``.
|
||||
|
||||
Starting in version 0.5.3, if you create a Unix group called *docker*
|
||||
and add users to it, then the ``docker`` daemon will make the
|
||||
ownership of the Unix socket read/writable by the *docker* group when
|
||||
the daemon starts. The ``docker`` daemon must always run as root, but
|
||||
if you run the ``docker`` client as a user in the *docker* group then
|
||||
you don't need to add ``sudo`` to all the client commands.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Add the docker group
|
||||
sudo groupadd docker
|
||||
|
||||
# Add the ubuntu user to the docker group
|
||||
# You may have to logout and log back in again for
|
||||
# this to take effect
|
||||
sudo gpasswd -a ubuntu docker
|
||||
|
||||
# Restart the docker daemon
|
||||
sudo service docker restart
|
||||
|
||||
Bind Docker to another host/port or a Unix socket
|
||||
-------------------------------------------------
|
||||
|
||||
With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
|
||||
.. DANGER:: Changing the default ``docker`` daemon binding to a TCP
|
||||
port or Unix *docker* user group will increase your security risks
|
||||
by allowing non-root users to potentially gain *root* access on the
|
||||
host (`e.g. #1369
|
||||
<https://github.com/dotcloud/docker/issues/1369>`_). Make sure you
|
||||
control access to ``docker``.
|
||||
|
||||
Similarly, the Docker client can use -H to connect to a custom port.
|
||||
With -H it is possible to make the Docker daemon to listen on a
|
||||
specific ip and port. By default, it will listen on
|
||||
``unix:///var/run/docker.sock`` to allow only local connections by the
|
||||
*root* user. You *could* set it to 0.0.0.0:4243 or a specific host ip to
|
||||
give access to everybody, but that is **not recommended** because then
|
||||
it is trivial for someone to gain root access to the host where the
|
||||
daemon is running.
|
||||
|
||||
Similarly, the Docker client can use ``-H`` to connect to a custom port.
|
||||
|
||||
``-H`` accepts host and port assignment in the following format:
|
||||
``tcp://[host][:port]`` or ``unix://path``
|
||||
|
||||
-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
|
||||
For example:
|
||||
|
||||
* tcp://host -> tcp connection on host:4243
|
||||
* tcp://host:port -> tcp connection on host:port
|
||||
* tcp://:port -> tcp connection on 127.0.0.1:port
|
||||
* unix://path/to/socket -> unix socket located at path/to/socket
|
||||
* ``tcp://host:4243`` -> tcp connection on host:4243
|
||||
* ``unix://path/to/socket`` -> unix socket located at ``path/to/socket``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
sudo <path to>/docker -H 0.0.0.0:5555 &
|
||||
# Download a base image
|
||||
docker -H :5555 pull base
|
||||
sudo <path to>/docker -H 0.0.0.0:5555 -d &
|
||||
# Download an ubuntu image
|
||||
sudo docker -H :5555 pull ubuntu
|
||||
|
||||
You can use multiple -H, for example, if you want to listen
|
||||
on both tcp and a unix socket
|
||||
You can use multiple ``-H``, for example, if you want to listen on
|
||||
both TCP and a Unix socket
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
|
||||
# Download a base image
|
||||
docker pull base
|
||||
# OR
|
||||
docker -H unix:///var/run/docker.sock pull base
|
||||
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d &
|
||||
# Download an ubuntu image, use default Unix socket
|
||||
sudo docker pull ubuntu
|
||||
# OR use the TCP port
|
||||
sudo docker -H tcp://127.0.0.1:4243 pull ubuntu
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
@@ -73,13 +116,13 @@ Starting a long-running worker process
|
||||
.. code-block:: bash
|
||||
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
JOB=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
|
||||
# Collect the output of the job so far
|
||||
docker logs $JOB
|
||||
sudo docker logs $JOB
|
||||
|
||||
# Kill the job
|
||||
docker kill $JOB
|
||||
sudo docker kill $JOB
|
||||
|
||||
|
||||
Listing all running containers
|
||||
@@ -87,7 +130,7 @@ Listing all running containers
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
sudo docker ps
|
||||
|
||||
Expose a service on a TCP port
|
||||
------------------------------
|
||||
@@ -95,10 +138,10 @@ Expose a service on a TCP port
|
||||
.. code-block:: bash
|
||||
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
|
||||
JOB=$(sudo docker run -d -p 4444 ubuntu /bin/nc -l -p 4444)
|
||||
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(docker port $JOB 4444)
|
||||
PORT=$(sudo docker port $JOB 4444)
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
|
||||
@@ -107,7 +150,7 @@ Expose a service on a TCP port
|
||||
echo hello world | nc $IP $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
echo "Daemon received: $(sudo docker logs $JOB)"
|
||||
|
||||
|
||||
Committing (saving) a container state
|
||||
@@ -115,21 +158,23 @@ Committing (saving) a container state
|
||||
|
||||
Save your containers state to a container image, so the state can be re-used.
|
||||
|
||||
When you commit your container only the differences between the image the container was created from
|
||||
and the current state of the container will be stored (as a diff). See which images you already have
|
||||
using ``docker images``
|
||||
When you commit your container only the differences between the image
|
||||
the container was created from and the current state of the container
|
||||
will be stored (as a diff). See which images you already have using
|
||||
``sudo docker images``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Commit your container to a new named image
|
||||
docker commit <container_id> <some_name>
|
||||
sudo docker commit <container_id> <some_name>
|
||||
|
||||
# List your containers
|
||||
docker images
|
||||
sudo docker images
|
||||
|
||||
You now have a image state from which you can create new instances.
|
||||
|
||||
|
||||
|
||||
Read more about :ref:`working_with_the_repository` or continue to the complete :ref:`cli`
|
||||
Read more about :ref:`working_with_the_repository` or continue to the
|
||||
complete :ref:`cli`
|
||||
|
||||
|
||||
+135
-30
@@ -1,6 +1,8 @@
|
||||
:title: Dockerfile Builder
|
||||
:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image.
|
||||
:keywords: builder, docker, Docker Builder, automation, image creation
|
||||
:title: Dockerfiles for Images
|
||||
:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image.
|
||||
:keywords: builder, docker, Dockerfile, automation, image creation
|
||||
|
||||
.. _dockerbuilder:
|
||||
|
||||
==================
|
||||
Dockerfile Builder
|
||||
@@ -23,14 +25,14 @@ describe the steps to assemble the image.
|
||||
Then call ``docker build`` with the path of your source repository as
|
||||
argument:
|
||||
|
||||
``docker build .``
|
||||
``sudo docker build .``
|
||||
|
||||
You can specify a repository and tag at which to save the new image if the
|
||||
build succeeds:
|
||||
|
||||
``docker build -t shykes/myapp .``
|
||||
``sudo docker build -t shykes/myapp .``
|
||||
|
||||
Docker will run your steps one-by-one, committing the result if necessary,
|
||||
Docker will run your steps one-by-one, committing the result if necessary,
|
||||
before finally outputting the ID of your new image.
|
||||
|
||||
2. Format
|
||||
@@ -43,7 +45,7 @@ The Dockerfile format is quite simple:
|
||||
# Comment
|
||||
INSTRUCTION arguments
|
||||
|
||||
The Instruction is not case-sensitive, however convention is for them to be
|
||||
The Instruction is not case-sensitive, however convention is for them to be
|
||||
UPPERCASE in order to distinguish them from arguments more easily.
|
||||
|
||||
Docker evaluates the instructions in a Dockerfile in order. **The first
|
||||
@@ -100,15 +102,54 @@ control.
|
||||
3.4 CMD
|
||||
-------
|
||||
|
||||
``CMD <command>``
|
||||
CMD has three forms:
|
||||
|
||||
The ``CMD`` instruction sets the command to be executed when running
|
||||
the image. This is functionally equivalent to running ``docker commit
|
||||
-run '{"Cmd": <command>}'`` outside the builder.
|
||||
* ``CMD ["executable","param1","param2"]`` (like an *exec*, preferred form)
|
||||
* ``CMD ["param1","param2"]`` (as *default parameters to ENTRYPOINT*)
|
||||
* ``CMD command param1 param2`` (as a *shell*)
|
||||
|
||||
.. note::
|
||||
Don't confuse `RUN` with `CMD`. `RUN` actually runs a
|
||||
command and commits the result; `CMD` does not execute anything at
|
||||
There can only be one CMD in a Dockerfile. If you list more than one
|
||||
CMD then only the last CMD will take effect.
|
||||
|
||||
**The main purpose of a CMD is to provide defaults for an executing
|
||||
container.** These defaults can include an executable, or they can
|
||||
omit the executable, in which case you must specify an ENTRYPOINT as
|
||||
well.
|
||||
|
||||
When used in the shell or exec formats, the ``CMD`` instruction sets
|
||||
the command to be executed when running the image. This is
|
||||
functionally equivalent to running ``docker commit -run '{"Cmd":
|
||||
<command>}'`` outside the builder.
|
||||
|
||||
If you use the *shell* form of the CMD, then the ``<command>`` will
|
||||
execute in ``/bin/sh -c``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu
|
||||
CMD echo "This is a test." | wc -
|
||||
|
||||
If you want to **run your** ``<command>`` **without a shell** then you
|
||||
must express the command as a JSON array and give the full path to the
|
||||
executable. **This array form is the preferred format of CMD.** Any
|
||||
additional parameters must be individually expressed as strings in the
|
||||
array:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu
|
||||
CMD ["/usr/bin/wc","--help"]
|
||||
|
||||
If you would like your container to run the same executable every
|
||||
time, then you should consider using ``ENTRYPOINT`` in combination
|
||||
with ``CMD``. See :ref:`entrypoint_def`.
|
||||
|
||||
If the user specifies arguments to ``docker run`` then they will
|
||||
override the default specified in CMD.
|
||||
|
||||
.. note::
|
||||
Don't confuse ``RUN`` with ``CMD``. ``RUN`` actually runs a
|
||||
command and commits the result; ``CMD`` does not execute anything at
|
||||
build time, but specifies the intended command for the image.
|
||||
|
||||
3.5 EXPOSE
|
||||
@@ -131,7 +172,7 @@ value ``<value>``. This value will be passed to all future ``RUN``
|
||||
instructions. This is functionally equivalent to prefixing the command
|
||||
with ``<key>=<value>``
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
The environment variables will persist when a container is run
|
||||
from the resulting image.
|
||||
|
||||
@@ -152,16 +193,24 @@ destination container.
|
||||
|
||||
The copy obeys the following rules:
|
||||
|
||||
* If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash,
|
||||
then a file is downloaded from the URL and copied to ``<dest>``.
|
||||
* If ``<src>`` is a URL and ``<dest>`` does end with a trailing slash,
|
||||
then the filename is inferred from the URL and the file is downloaded to
|
||||
``<dest>/<filename>``. For instance, ``ADD http://example.com/foobar /``
|
||||
would create the file ``/foobar``. The URL must have a nontrivial path
|
||||
so that an appropriate filename can be discovered in this case
|
||||
(``http://example.com`` will not work).
|
||||
* If ``<src>`` is a directory, the entire directory is copied,
|
||||
including filesystem metadata.
|
||||
* If ``<src>``` is a tar archive in a recognized compression format
|
||||
(identity, gzip, bzip2 or xz), it is unpacked as a directory.
|
||||
|
||||
When a directory is copied or unpacked, it has the same behavior as
|
||||
``tar -x``: the result is the union of
|
||||
``tar -x``: the result is the union of
|
||||
|
||||
1. whatever existed at the destination path and
|
||||
2. the contents of the source tree,
|
||||
2. the contents of the source tree,
|
||||
|
||||
with conflicts resolved in favor of 2) on a file-by-file basis.
|
||||
|
||||
@@ -174,18 +223,57 @@ The copy obeys the following rules:
|
||||
written at ``<dst>``.
|
||||
* If ``<dest>`` doesn't exist, it is created along with all missing
|
||||
directories in its path. All new files and directories are created
|
||||
with mode 0700, uid and gid 0.
|
||||
with mode 0755, uid and gid 0.
|
||||
|
||||
.. _entrypoint_def:
|
||||
|
||||
3.8 ENTRYPOINT
|
||||
-------------
|
||||
--------------
|
||||
|
||||
``ENTRYPOINT /bin/echo``
|
||||
ENTRYPOINT has two forms:
|
||||
|
||||
The ``ENTRYPOINT`` instruction adds an entry command that will not be
|
||||
overwritten when arguments are passed to docker run, unlike the
|
||||
* ``ENTRYPOINT ["executable", "param1", "param2"]`` (like an *exec*,
|
||||
preferred form)
|
||||
* ``ENTRYPOINT command param1 param2`` (as a *shell*)
|
||||
|
||||
There can only be one ``ENTRYPOINT`` in a Dockerfile. If you have more
|
||||
than one ``ENTRYPOINT``, then only the last one in the Dockerfile will
|
||||
have an effect.
|
||||
|
||||
An ``ENTRYPOINT`` helps you to configure a container that you can run
|
||||
as an executable. That is, when you specify an ``ENTRYPOINT``, then
|
||||
the whole container runs as if it was just that executable.
|
||||
|
||||
The ``ENTRYPOINT`` instruction adds an entry command that will **not**
|
||||
be overwritten when arguments are passed to ``docker run``, unlike the
|
||||
behavior of ``CMD``. This allows arguments to be passed to the
|
||||
entrypoint. i.e. ``docker run <image> -d`` will pass the "-d" argument
|
||||
to the entrypoint.
|
||||
entrypoint. i.e. ``docker run <image> -d`` will pass the "-d"
|
||||
argument to the ENTRYPOINT.
|
||||
|
||||
You can specify parameters either in the ENTRYPOINT JSON array (as in
|
||||
"like an exec" above), or by using a CMD statement. Parameters in the
|
||||
ENTRYPOINT will not be overridden by the ``docker run`` arguments, but
|
||||
parameters specified via CMD will be overridden by ``docker run``
|
||||
arguments.
|
||||
|
||||
Like a ``CMD``, you can specify a plain string for the ENTRYPOINT and
|
||||
it will execute in ``/bin/sh -c``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu
|
||||
ENTRYPOINT wc -l -
|
||||
|
||||
For example, that Dockerfile's image will *always* take stdin as input
|
||||
("-") and print the number of lines ("-l"). If you wanted to make
|
||||
this optional but default, you could use a CMD:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
FROM ubuntu
|
||||
CMD ["-l", "-"]
|
||||
ENTRYPOINT ["/usr/bin/wc"]
|
||||
|
||||
|
||||
3.9 VOLUME
|
||||
----------
|
||||
@@ -195,6 +283,23 @@ to the entrypoint.
|
||||
The ``VOLUME`` instruction will add one or more new volumes to any
|
||||
container created from the image.
|
||||
|
||||
3.10 USER
|
||||
---------
|
||||
|
||||
``USER daemon``
|
||||
|
||||
The ``USER`` instruction sets the username or UID to use when running
|
||||
the image.
|
||||
|
||||
3.11 WORKDIR
|
||||
------------
|
||||
|
||||
``WORKDIR /path/to/workdir``
|
||||
|
||||
The ``WORKDIR`` instruction sets the working directory in which
|
||||
the command given by ``CMD`` is executed.
|
||||
|
||||
|
||||
4. Dockerfile Examples
|
||||
======================
|
||||
|
||||
@@ -203,14 +308,14 @@ container created from the image.
|
||||
# Nginx
|
||||
#
|
||||
# VERSION 0.0.1
|
||||
|
||||
|
||||
FROM ubuntu
|
||||
MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
|
||||
|
||||
|
||||
# make sure the package repository is up to date
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
|
||||
|
||||
RUN apt-get install -y inotify-tools nginx apache2 openssh-server
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -218,12 +323,12 @@ container created from the image.
|
||||
# Firefox over VNC
|
||||
#
|
||||
# VERSION 0.3
|
||||
|
||||
|
||||
FROM ubuntu
|
||||
# make sure the package repository is up to date
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
|
||||
|
||||
# Install vnc, xvfb in order to create a 'fake' display and firefox
|
||||
RUN apt-get install -y x11vnc xvfb firefox
|
||||
RUN mkdir /.vnc
|
||||
@@ -231,7 +336,7 @@ container created from the image.
|
||||
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
|
||||
# Autostart firefox (might not be the best way, but it does the trick)
|
||||
RUN bash -c 'echo "firefox" >> /.bashrc'
|
||||
|
||||
|
||||
EXPOSE 5900
|
||||
CMD ["x11vnc", "-forever", "-usepw", "-create"]
|
||||
|
||||
|
||||
@@ -6,20 +6,23 @@
|
||||
Port redirection
|
||||
================
|
||||
|
||||
Docker can redirect public tcp ports to your container, so it can be reached over the network.
|
||||
Port redirection is done on ``docker run`` using the -p flag.
|
||||
Docker can redirect public TCP ports to your container, so it can be
|
||||
reached over the network. Port redirection is done on ``docker run``
|
||||
using the -p flag.
|
||||
|
||||
A port redirect is specified as PUBLIC:PRIVATE, where tcp port PUBLIC will be redirected to
|
||||
tcp port PRIVATE. As a special case, the public port can be omitted, in which case a random
|
||||
public port will be allocated.
|
||||
A port redirect is specified as *PUBLIC:PRIVATE*, where TCP port
|
||||
*PUBLIC* will be redirected to TCP port *PRIVATE*. As a special case,
|
||||
the public port can be omitted, in which case a random public port
|
||||
will be allocated.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# A random PUBLIC port is redirected to PRIVATE port 80 on the container
|
||||
docker run -p 80 <image> <cmd>
|
||||
sudo docker run -p 80 <image> <cmd>
|
||||
|
||||
# PUBLIC port 80 is redirected to PRIVATE port 80
|
||||
docker run -p 80:80 <image> <cmd>
|
||||
sudo docker run -p 80:80 <image> <cmd>
|
||||
|
||||
|
||||
Default port redirects can be built into a container with the EXPOSE build command.
|
||||
Default port redirects can be built into a container with the
|
||||
``EXPOSE`` build command.
|
||||
|
||||
+17
-13
@@ -9,28 +9,32 @@ Using Puppet
|
||||
|
||||
.. note::
|
||||
|
||||
Please note this is a community contributed installation path. The only 'official' installation is using the
|
||||
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
|
||||
Please note this is a community contributed installation path. The
|
||||
only 'official' installation is using the :ref:`ubuntu_linux`
|
||||
installation path. This version may sometimes be out of date.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
To use this guide you'll need a working installation of Puppet from `Puppetlabs <https://www.puppetlabs.com>`_ .
|
||||
To use this guide you'll need a working installation of Puppet from
|
||||
`Puppetlabs <https://www.puppetlabs.com>`_ .
|
||||
|
||||
The module also currently uses the official PPA so only works with Ubuntu.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The module is available on the `Puppet Forge <https://forge.puppetlabs.com/garethr/docker/>`_
|
||||
and can be installed using the built-in module tool.
|
||||
The module is available on the `Puppet Forge
|
||||
<https://forge.puppetlabs.com/garethr/docker/>`_ and can be installed
|
||||
using the built-in module tool.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
puppet module install garethr/docker
|
||||
|
||||
It can also be found on `GitHub <https://www.github.com/garethr/garethr-docker>`_
|
||||
if you would rather download the source.
|
||||
It can also be found on `GitHub
|
||||
<https://www.github.com/garethr/garethr-docker>`_ if you would rather
|
||||
download the source.
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -53,13 +57,13 @@ defined type which can be used like so:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
docker::image { 'base': }
|
||||
docker::image { 'ubuntu': }
|
||||
|
||||
This is equivalent to running:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker pull base
|
||||
docker pull ubuntu
|
||||
|
||||
Note that it will only if the image of that name does not already exist.
|
||||
This is downloading a large binary so on first run can take a while.
|
||||
@@ -68,7 +72,7 @@ for exec. Note that you can also remove images you no longer need with:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
docker::image { 'base':
|
||||
docker::image { 'ubuntu':
|
||||
ensure => 'absent',
|
||||
}
|
||||
|
||||
@@ -81,7 +85,7 @@ docker.
|
||||
.. code-block:: ruby
|
||||
|
||||
docker::run { 'helloworld':
|
||||
image => 'base',
|
||||
image => 'ubuntu',
|
||||
command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
|
||||
}
|
||||
|
||||
@@ -89,14 +93,14 @@ This is equivalent to running the following command, but under upstart:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
|
||||
|
||||
Run also contains a number of optional parameters:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
docker::run { 'helloworld':
|
||||
image => 'base',
|
||||
image => 'ubuntu',
|
||||
command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
|
||||
ports => ['4444', '4555'],
|
||||
volumes => ['/var/lib/counchdb', '/var/log'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:title: Working With Repositories
|
||||
:description: Repositories allow users to share images.
|
||||
:keywords: repo, repositiores, usage, pull image, push image, image, documentation
|
||||
:keywords: repo, repositories, usage, pull image, push image, image, documentation
|
||||
|
||||
.. _working_with_the_repository:
|
||||
|
||||
@@ -57,10 +57,10 @@ address of the registry's host, like this:
|
||||
# Tag to create a repository with the full registry location.
|
||||
# The location (e.g. localhost.localdomain:5000) becomes
|
||||
# a permanent part of the repository name
|
||||
docker tag 0u812deadbeef localhost.localdomain:5000/repo_name
|
||||
sudo docker tag 0u812deadbeef localhost.localdomain:5000/repo_name
|
||||
|
||||
# Push the new repository to its home location on localhost
|
||||
docker push localhost.localdomain:5000/repo_name
|
||||
sudo docker push localhost.localdomain:5000/repo_name
|
||||
|
||||
Once a repository has your registry's host name as part of the tag,
|
||||
you can push and pull it like any other repository, but it will
|
||||
@@ -71,18 +71,18 @@ function completely independently from the Central Index.
|
||||
Find public images available on the Central Index
|
||||
-------------------------------------------------
|
||||
|
||||
Seach by name, namespace or description
|
||||
Search by name, namespace or description
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker search <value>
|
||||
sudo docker search <value>
|
||||
|
||||
|
||||
Download them simply by their name
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker pull <value>
|
||||
sudo docker pull <value>
|
||||
|
||||
|
||||
Very similarly you can search for and browse the index online on
|
||||
@@ -96,7 +96,7 @@ You can create a user on the central Docker Index online, or by running
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker login
|
||||
sudo docker login
|
||||
|
||||
This will prompt you for a username, which will become a public
|
||||
namespace for your public repositories.
|
||||
@@ -115,11 +115,11 @@ your container to an image within your username namespace.
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker commit $CONTAINER_ID dhrp/kickassapp
|
||||
docker commit <container_id> <username>/<repo_name>
|
||||
sudo docker commit <container_id> <username>/<repo_name>
|
||||
|
||||
|
||||
Pushing a container to its repository
|
||||
------------------------------------
|
||||
-------------------------------------
|
||||
|
||||
In order to push an image to its repository you need to have committed
|
||||
your container to a named image (see above)
|
||||
@@ -129,4 +129,4 @@ Now you can commit this image to the repository
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker push dhrp/kickassapp
|
||||
docker push <username>/<repo_name>
|
||||
sudo docker push <username>/<repo_name>
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
Thatcher Penskens <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com> (@dhrp)
|
||||
|
||||
Vendored
+9
-15
@@ -68,19 +68,18 @@
|
||||
|
||||
<div style="float: right" class="pull-right">
|
||||
<ul class="nav">
|
||||
<li id="nav-introduction"><a href="http://www.docker.io/">Introduction</a></li>
|
||||
<li id="nav-introduction"><a href="http://www.docker.io/" title="Docker Homepage">Home</a></li>
|
||||
<li id="nav-about"><a href="http://www.docker.io/about/" title="About">About</a></li>
|
||||
<li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
|
||||
<li id="nav-community"><a href="http://www.docker.io/community/" title="Community">Community</a></li>
|
||||
<li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
||||
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
|
||||
<li id="nav-blog"><a href="http://blog.docker.io/" title="Docker Blog">Blog</a></li>
|
||||
<li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
|
||||
</ul>
|
||||
<!--<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">-->
|
||||
<!--<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>-->
|
||||
<!--<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>-->
|
||||
<!--</div>-->
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="http://www.docker.io"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
|
||||
<a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; width: 160px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,9 +93,9 @@
|
||||
<div class="span12 titlebar">
|
||||
<!--<span class="pull-right" style="margin-left: 20px; font-size: 20px">{{version}}</span>-->
|
||||
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
|
||||
<a href="http://github.com/dotcloud/docker/"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Fork us on Github</a>
|
||||
<a href="https://github.com/dotcloud/docker/blob/master/docs/sources/{{ pagename }}.rst"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Edit this page on Github</a>
|
||||
</div>
|
||||
<h1 class="pageheader">DOCUMENTATION</h1>
|
||||
<h1 class="pageheader"><a href="http://docs.docker.io/en/latest/" title="Documentation" style="color: white;">DOCUMENTATION</a></h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,7 +130,7 @@
|
||||
|
||||
<div class="span12 footer">
|
||||
<div class="tbox textright forceleftmargin social links pull-right">
|
||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
||||
<a class="twitter" href="http://twitter.com/docker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
</div>
|
||||
|
||||
@@ -157,11 +156,6 @@
|
||||
{# {%- endif %}#}
|
||||
|
||||
|
||||
{##}
|
||||
{# <div class="links" style="float: right;">#}
|
||||
{# <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>#}
|
||||
{# <a class="github" href="http://github.com/dotcloud/docker/">GitHub</a>#}
|
||||
{# </div>#}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+21
-5
@@ -34,12 +34,12 @@ h4 {
|
||||
.navbar .nav li a {
|
||||
padding: 22px 15px 22px;
|
||||
}
|
||||
.navbar .brand {
|
||||
padding: 13px 10px 13px 28px ;
|
||||
}
|
||||
.navbar-dotcloud .container {
|
||||
border-bottom: 2px #000000 solid;
|
||||
}
|
||||
.inline-icon {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
* http://www.jonsuh.com
|
||||
@@ -82,7 +82,7 @@ h4 {
|
||||
.btn-custom {
|
||||
background-color: #292929 !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
@@ -301,7 +301,7 @@ section.header {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
background-color: #43484c;
|
||||
filter: progid:dximagetransform.microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c));
|
||||
background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
@@ -375,3 +375,19 @@ section.header {
|
||||
table th {
|
||||
text-align: left;
|
||||
}
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
.headerlink {
|
||||
font-size: smaller;
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
+21
-7
@@ -53,13 +53,6 @@ h1, h2, h3, h4 {
|
||||
padding: 22px 15px 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
padding: 13px 10px 13px 28px ;
|
||||
// padding-left: 30px;
|
||||
|
||||
}
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
@@ -67,6 +60,9 @@ h1, h2, h3, h4 {
|
||||
border-bottom: 2px @black solid;
|
||||
}
|
||||
|
||||
.inline-icon {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
@@ -490,4 +486,22 @@ section.header {
|
||||
/* Misc fixes */
|
||||
table th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.headerlink {
|
||||
font-size: smaller;
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
visibility: hidden;
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1 +0,0 @@
|
||||
Thatcher Penskens <thatcher@dotcloud.com>
|
||||
@@ -1,2 +0,0 @@
|
||||
www:
|
||||
type: static
|
||||
@@ -1,220 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!-->
|
||||
<html class="no-js" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Docker - the Linux container runtime</title>
|
||||
|
||||
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- twitter bootstrap -->
|
||||
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="../static/css/bootstrap-responsive.min.css">
|
||||
|
||||
<!-- main style file -->
|
||||
<link rel="stylesheet" href="../static/css/main.css">
|
||||
|
||||
<!-- vendor scripts -->
|
||||
<script src="../static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="../static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-dotcloud">
|
||||
<div class="container" style="text-align: center;">
|
||||
|
||||
<div style="float: right" class="pull-right">
|
||||
<ul class="nav">
|
||||
<li id="nav-introduction"><a href="../">Introduction</a></li>
|
||||
<li id="nav-gettingstarted" class="active"><a href="">Getting started</a></li>
|
||||
<li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
||||
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../static/img/docker-letters-logo.gif"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="span12 titlebar">
|
||||
|
||||
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
|
||||
<a href="http://github.com/dotcloud/docker/"><img src="../static/img/fork-us.png"> Fork us on Github</a>
|
||||
</div>
|
||||
|
||||
<h1 class="pageheader"> GETTING STARTED</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="alert alert-info" style="margin-bottom: 0;">
|
||||
<strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h2>
|
||||
<a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
|
||||
</a>Installing on Ubuntu</h2>
|
||||
|
||||
<p><strong>Requirements</strong></p>
|
||||
<ul>
|
||||
<li>Ubuntu 12.04 (LTS) (64-bit)</li>
|
||||
<li> or Ubuntu 12.10 (quantal) (64-bit)</li>
|
||||
<li>The 3.8 Linux Kernel</li>
|
||||
</ul>
|
||||
<ol>
|
||||
<li>
|
||||
<p><strong>Install dependencies</strong></p>
|
||||
The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
||||
<pre>sudo apt-get install linux-image-extra-`uname -r`</pre>
|
||||
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Install Docker</strong></p>
|
||||
<p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
|
||||
<p>This may import a new GPG key (key 63561DC6: public key "Launchpad PPA for dotcloud team" imported).</p>
|
||||
<div class="highlight">
|
||||
<pre>sudo apt-get install software-properties-common</pre>
|
||||
<pre>sudo add-apt-repository ppa:dotcloud/lxc-docker</pre>
|
||||
<pre>sudo apt-get update</pre>
|
||||
<pre>sudo apt-get install lxc-docker</pre>
|
||||
</div>
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p><strong>Run!</strong></p>
|
||||
|
||||
<div class="highlight">
|
||||
<pre>docker run -i -t ubuntu /bin/bash</pre>
|
||||
</div>
|
||||
</li>
|
||||
Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.<br>
|
||||
Or check <a href="http://docs.docker.io/en/latest/installation/ubuntulinux/">more detailed installation instructions</a>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="contentblock">
|
||||
<h2>Contributing to Docker</h2>
|
||||
|
||||
<p>Want to hack on Docker? Awesome! We have some <a href="http://docs.docker.io/en/latest/contributing/contributing/">instructions to get you started</a>. They are probably not perfect, please let us know if anything feels wrong or incomplete.</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h2>Quick install on other operating systems</h2>
|
||||
<p><strong>For other operating systems we recommend and provide a streamlined install with virtualbox,
|
||||
vagrant and an Ubuntu virtual machine.</strong></p>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://docs.docker.io/en/latest/installation/vagrant/">Mac OS X and other linuxes</a></li>
|
||||
<li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="contentblock">
|
||||
<h2>Questions? Want to get in touch?</h2>
|
||||
<p>There are several ways to get in touch:</p>
|
||||
<p><strong>Join the discussion on IRC.</strong> We can be found in the <a href="irc://chat.freenode.net#docker">#docker</a> channel on chat.freenode.net</p>
|
||||
<p><strong>Discussions</strong> happen on our google group: <a href="https://groups.google.com/d/forum/docker-club">docker-club at googlegroups.com</a></p>
|
||||
<p>All our <strong>development and decisions</strong> are made out in the open on Github <a href="http://www.github.com/dotcloud/docker">github.com/dotcloud/docker</a></p>
|
||||
<p><strong>Get help on using Docker</strong> by asking on <a href="http://stackoverflow.com/tags/docker/">Stackoverflow</a></p>
|
||||
<p>And of course, <strong>tweet</strong> your tweets to <a href="http://twitter.com/getdocker/">twitter.com/getdocker</a></p>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="contentblock">
|
||||
<div id="wufoo-z7x3p3">
|
||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
||||
</div>
|
||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
||||
var s = d.createElement(t), options = {
|
||||
'userName':'dotclouddocker',
|
||||
'formHash':'z7x3p3',
|
||||
'autoResize':true,
|
||||
'height':'577',
|
||||
'async':true,
|
||||
'header':'show'};
|
||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
||||
s.onload = s.onreadystatechange = function() {
|
||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
||||
})(document, 'script');</script>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<footer id="footer" class="footer">
|
||||
<div class="row">
|
||||
<div class="span12 social">
|
||||
<div class="tbox textright forceleftmargin social links pull-right">
|
||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
</div>
|
||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="emptyspace" style="height: 40px">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- bootstrap javascipts -->
|
||||
<script src="../static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
_gaq.push(['_setDomainName', 'docker.io']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,359 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
||||
<!--[if gt IE 8]><!-->
|
||||
<html class="no-js" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
|
||||
<title>Docker - the Linux container engine</title>
|
||||
|
||||
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- twitter bootstrap -->
|
||||
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="static/css/bootstrap-responsive.min.css">
|
||||
|
||||
<!-- main style file -->
|
||||
<link rel="stylesheet" href="static/css/main.css">
|
||||
|
||||
<!-- vendor scripts -->
|
||||
<script src="static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
||||
<script src="static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
||||
|
||||
<style>
|
||||
.indexlabel {
|
||||
float: left;
|
||||
width: 150px;
|
||||
display: block;
|
||||
padding: 10px 20px 10px;
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
background-color: #a30000;
|
||||
color: white;
|
||||
height: 22px;
|
||||
}
|
||||
.searchbutton {
|
||||
font-size: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.debug {
|
||||
border: 1px red dotted;
|
||||
}
|
||||
.twitterblock {
|
||||
min-height: 75px;
|
||||
}
|
||||
|
||||
.twitterblock img {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-dotcloud">
|
||||
<div class="container" style="text-align: center;">
|
||||
|
||||
|
||||
<div class="pull-left" id="fork-us" style="margin-top: 16px;">
|
||||
<a href="http://github.com/dotcloud/docker/"><img src="static/img/fork-us.png" alt="fork-icon"> Fork us on Github</a>
|
||||
</div>
|
||||
|
||||
<div class="pull-right" >
|
||||
<ul class="nav">
|
||||
<li id="nav-introduction" class="active"><a href="/">Introduction</a></li>
|
||||
<li id="nav-gettingstarted"><a href="gettingstarted">Getting started</a></li>
|
||||
<li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
||||
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container" style="margin-top: 30px;">
|
||||
<div class="row">
|
||||
|
||||
<div class="span12">
|
||||
<section class="contentblock header">
|
||||
|
||||
<div class="span5" style="margin-bottom: 15px;">
|
||||
<div style="text-align: center;" >
|
||||
<img src="static/img/docker_letters_500px.png" alt="docker letters">
|
||||
|
||||
<h2>The Linux container engine</h2>
|
||||
</div>
|
||||
|
||||
<div style="display: block; text-align: center; margin-top: 20px;">
|
||||
|
||||
<h5>
|
||||
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
|
||||
</h5>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div style="display: block; text-align: center; margin-top: 30px;">
|
||||
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="span6" >
|
||||
<div class="js-video" >
|
||||
<iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br style="clear: both"/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h4>Heterogeneous payloads</h4>
|
||||
<p>Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.</p>
|
||||
<h4>Any server</h4>
|
||||
<p>Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.</p>
|
||||
<h4>Isolation</h4>
|
||||
<p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
|
||||
<h4>Repeatability</h4>
|
||||
<p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
|
||||
</section>
|
||||
<section class="contentblock">
|
||||
<div class="container">
|
||||
<div class="span2" style="margin-left: 0" >
|
||||
<a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description"><img src="static/img/hiring_graphic.png" alt="we're hiring" width="140" style="margin-top: 25px"></a>
|
||||
</div>
|
||||
<div class="span4" style="margin-left: 0">
|
||||
<h4>Do you think it is cool to hack on docker? Join us!</h4>
|
||||
<ul>
|
||||
<li>Work on open source</li>
|
||||
<li>Program in Go</li>
|
||||
</ul>
|
||||
<a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description">read more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock">
|
||||
<h1>New! Docker Index</h1>
|
||||
On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
|
||||
|
||||
<br><br>
|
||||
<a href="https://index.docker.io" target="_blank">
|
||||
<div class="indexlabel">
|
||||
DOCKER index
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<input type="button" class="searchbutton" value="Search images"
|
||||
onClick="window.open('https://index.docker.io')" />
|
||||
|
||||
</section>
|
||||
<section class="contentblock">
|
||||
<div id="wufoo-z7x3p3">
|
||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
||||
</div>
|
||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
||||
var s = d.createElement(t), options = {
|
||||
'userName':'dotclouddocker',
|
||||
'formHash':'z7x3p3',
|
||||
'autoResize':true,
|
||||
'height':'577',
|
||||
'async':true,
|
||||
'header':'show'};
|
||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
||||
s.onload = s.onreadystatechange = function() {
|
||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
||||
})(document, 'script');</script>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/2707460527/252a64411a339184ff375a96fb68dcb0_bigger.png">
|
||||
<em>Mitchell Hashimoto @mitchellh:</em> Docker launched today. It is incredible. They’re also working RIGHT NOW on a Vagrant provider. LXC is COMING!!
|
||||
</section>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/1108290260/Adam_Jacob-114x150_original_bigger.jpg">
|
||||
<em>Adam Jacob @adamhjk:</em> Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think.
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/14872832/twitter_pic_bigger.jpg">
|
||||
<em>Matt Townsend @mtownsend:</em> I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego.
|
||||
</section>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/1312352395/rupert-259x300_bigger.jpg">
|
||||
<em>Rob Harrop @robertharrop:</em> Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter.
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://twimg0-a.akamaihd.net/profile_images/2491994496/rbevyyq6ykp6bnoby2je_bigger.jpeg">
|
||||
<em>John Willis @botchagalupe:</em> IMHO docker is to paas what chef was to Iaas 4 years ago
|
||||
</section>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://twimg0-a.akamaihd.net/profile_images/3348427561/9d7f08f1e103a16c8debd169301b9944_bigger.jpeg">
|
||||
<em>John Feminella @superninjarobot:</em> So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam!
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/3408403010/4496ccdd14e9b7285eca04c31a740207_bigger.jpeg">
|
||||
<em>David Romulan @destructuring:</em> I haven't had this much fun since AWS
|
||||
</section>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<section class="contentblock twitterblock">
|
||||
<img src="https://si0.twimg.com/profile_images/780893320/My_Avatar_bigger.jpg">
|
||||
<em>Ricardo Gladwell @rgladwell:</em> wow @getdocker is either amazing or totally stupid
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span6">
|
||||
|
||||
<section class="contentblock">
|
||||
|
||||
<h2>Notable features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
|
||||
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
|
||||
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
|
||||
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.</li>
|
||||
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
|
||||
<li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
|
||||
<li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Under the hood</h2>
|
||||
|
||||
<p>Under the hood, Docker is built on the following components:</p>
|
||||
|
||||
<ul>
|
||||
<li>The <a href="http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c">cgroup</a> and <a href="http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part">namespacing</a> capabilities of the Linux kernel;</li>
|
||||
<li><a href="http://aufs.sourceforge.net/aufs.html">AUFS</a>, a powerful union filesystem with copy-on-write capabilities;</li>
|
||||
<li>The <a href="http://golang.org">Go</a> programming language;</li>
|
||||
<li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Who started it</h2>
|
||||
<p>
|
||||
Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
|
||||
|
||||
<p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
|
||||
of applications and databases.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="span6">
|
||||
|
||||
|
||||
<section class="contentblock">
|
||||
<h3 id="twitter">Twitter</h3>
|
||||
<a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- end container -->
|
||||
|
||||
|
||||
<div class="container">
|
||||
<footer id="footer" class="footer">
|
||||
<div class="row">
|
||||
<div class="span12">
|
||||
<div class="tbox textright forceleftmargin social links pull-right">
|
||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
||||
</div>
|
||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="emptyspace" style="height: 40px">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- bootstrap javascipts -->
|
||||
<script src="static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
||||
_gaq.push(['_setDomainName', 'docker.io']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
# rule to redirect original links created when hosted on github pages
|
||||
rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent;
|
||||
|
||||
# rewrite the stuff which was on the current page
|
||||
rewrite ^/gettingstarted.html$ /gettingstarted/ permanent;
|
||||
@@ -1 +0,0 @@
|
||||
../theme/docker/static
|
||||
@@ -1,9 +1,7 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -11,17 +9,13 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Graph is a store for versioned filesystem images and the relationship between them.
|
||||
type Graph struct {
|
||||
Root string
|
||||
idIndex *utils.TruncIndex
|
||||
checksumLock map[string]*sync.Mutex
|
||||
lockSumFile *sync.Mutex
|
||||
lockSumMap *sync.Mutex
|
||||
Root string
|
||||
idIndex *utils.TruncIndex
|
||||
}
|
||||
|
||||
// NewGraph instantiates a new graph at the given root path in the filesystem.
|
||||
@@ -36,11 +30,8 @@ func NewGraph(root string) (*Graph, error) {
|
||||
return nil, err
|
||||
}
|
||||
graph := &Graph{
|
||||
Root: abspath,
|
||||
idIndex: utils.NewTruncIndex(),
|
||||
checksumLock: make(map[string]*sync.Mutex),
|
||||
lockSumFile: &sync.Mutex{},
|
||||
lockSumMap: &sync.Mutex{},
|
||||
Root: abspath,
|
||||
idIndex: utils.NewTruncIndex(),
|
||||
}
|
||||
if err := graph.restore(); err != nil {
|
||||
return nil, err
|
||||
@@ -99,11 +90,6 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
graph.lockSumMap.Lock()
|
||||
defer graph.lockSumMap.Unlock()
|
||||
if _, exists := graph.checksumLock[img.ID]; !exists {
|
||||
graph.checksumLock[img.ID] = &sync.Mutex{}
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@@ -123,16 +109,15 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
|
||||
img.Container = container.ID
|
||||
img.ContainerConfig = *container.Config
|
||||
}
|
||||
if err := graph.Register(layerData, layerData != nil, img); err != nil {
|
||||
if err := graph.Register(nil, layerData, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go img.Checksum()
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// Register imports a pre-existing image into the graph.
|
||||
// FIXME: pass img as first argument
|
||||
func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
|
||||
func (graph *Graph) Register(jsonData []byte, layerData Archive, img *Image) error {
|
||||
if err := ValidateID(img.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -145,7 +130,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Mktemp failed: %s", err)
|
||||
}
|
||||
if err := StoreImage(img, layerData, tmp, store); err != nil {
|
||||
if err := StoreImage(img, jsonData, layerData, tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
// Commit
|
||||
@@ -154,7 +139,6 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
|
||||
}
|
||||
img.graph = graph
|
||||
graph.idIndex.Add(img.ID)
|
||||
graph.checksumLock[img.ID] = &sync.Mutex{}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -175,7 +159,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *uti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
|
||||
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root)
|
||||
}
|
||||
|
||||
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
|
||||
@@ -193,8 +177,65 @@ func (graph *Graph) Mktemp(id string) (string, error) {
|
||||
return tmp.imageRoot(id), nil
|
||||
}
|
||||
|
||||
// getDockerInitLayer returns the path of a layer containing a mountpoint suitable
|
||||
// for bind-mounting dockerinit into the container. The mountpoint is simply an
|
||||
// empty file at /.dockerinit
|
||||
//
|
||||
// This extra layer is used by all containers as the top-most ro layer. It protects
|
||||
// the container from unwanted side-effects on the rw layer.
|
||||
func (graph *Graph) getDockerInitLayer() (string, error) {
|
||||
tmp, err := graph.tmp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
initLayer := tmp.imageRoot("_dockerinit")
|
||||
if err := os.Mkdir(initLayer, 0755); err != nil && !os.IsExist(err) {
|
||||
// If directory already existed, keep going.
|
||||
// For all other errors, abort.
|
||||
return "", err
|
||||
}
|
||||
|
||||
for pth, typ := range map[string]string{
|
||||
"/dev/pts": "dir",
|
||||
"/dev/shm": "dir",
|
||||
"/proc": "dir",
|
||||
"/sys": "dir",
|
||||
"/.dockerinit": "file",
|
||||
"/etc/resolv.conf": "file",
|
||||
// "var/run": "dir",
|
||||
// "var/lock": "dir",
|
||||
} {
|
||||
if _, err := os.Stat(path.Join(initLayer, pth)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
switch typ {
|
||||
case "dir":
|
||||
if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
case "file":
|
||||
if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layer is ready to use, if it wasn't before.
|
||||
return initLayer, nil
|
||||
}
|
||||
|
||||
func (graph *Graph) tmp() (*Graph, error) {
|
||||
return NewGraph(path.Join(graph.Root, ":tmp:"))
|
||||
// Changed to _tmp from :tmp:, because it messed with ":" separators in aufs branch syntax...
|
||||
return NewGraph(path.Join(graph.Root, "_tmp"))
|
||||
}
|
||||
|
||||
// Check if given error is "not empty".
|
||||
@@ -282,9 +323,9 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) {
|
||||
return
|
||||
}
|
||||
if children, exists := byParent[parent.ID]; exists {
|
||||
byParent[parent.ID] = []*Image{image}
|
||||
} else {
|
||||
byParent[parent.ID] = append(children, image)
|
||||
} else {
|
||||
byParent[parent.ID] = []*Image{image}
|
||||
}
|
||||
})
|
||||
return byParent, err
|
||||
@@ -311,40 +352,3 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
|
||||
func (graph *Graph) imageRoot(id string) string {
|
||||
return path.Join(graph.Root, id)
|
||||
}
|
||||
|
||||
func (graph *Graph) getStoredChecksums() (map[string]string, error) {
|
||||
checksums := make(map[string]string)
|
||||
// FIXME: Store the checksum in memory
|
||||
|
||||
if checksumDict, err := ioutil.ReadFile(path.Join(graph.Root, "checksums")); err == nil {
|
||||
if err := json.Unmarshal(checksumDict, &checksums); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return checksums, nil
|
||||
}
|
||||
|
||||
func (graph *Graph) storeChecksums(checksums map[string]string) error {
|
||||
checksumJSON, err := json.Marshal(checksums)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJSON, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (graph *Graph) UpdateChecksums(newChecksums map[string]*registry.ImgData) error {
|
||||
graph.lockSumFile.Lock()
|
||||
defer graph.lockSumFile.Unlock()
|
||||
|
||||
localChecksums, err := graph.getStoredChecksums()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for id, elem := range newChecksums {
|
||||
localChecksums[id] = elem.Checksum
|
||||
}
|
||||
return graph.storeChecksums(localChecksums)
|
||||
}
|
||||
|
||||
+43
-4
@@ -38,7 +38,7 @@ func TestInterruptedRegister(t *testing.T) {
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
go graph.Register(badArchive, false, image)
|
||||
go graph.Register(nil, badArchive, image)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
|
||||
if _, err := graph.Get(image.ID); err == nil {
|
||||
@@ -49,7 +49,7 @@ func TestInterruptedRegister(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Register(goodArchive, false, image); err != nil {
|
||||
if err := graph.Register(nil, goodArchive, image); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func TestRegister(t *testing.T) {
|
||||
Comment: "testing",
|
||||
Created: time.Now(),
|
||||
}
|
||||
err = graph.Register(archive, false, image)
|
||||
err = graph.Register(nil, archive, image)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -225,7 +225,7 @@ func TestDelete(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Test delete twice (pull -> rm -> pull -> rm)
|
||||
if err := graph.Register(archive, false, img1); err != nil {
|
||||
if err := graph.Register(nil, archive, img1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := graph.Delete(img1.ID); err != nil {
|
||||
@@ -234,6 +234,45 @@ func TestDelete(t *testing.T) {
|
||||
assertNImages(graph, t, 1)
|
||||
}
|
||||
|
||||
func TestByParent(t *testing.T) {
|
||||
archive1, _ := fakeTar()
|
||||
archive2, _ := fakeTar()
|
||||
archive3, _ := fakeTar()
|
||||
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
parentImage := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "parent",
|
||||
Created: time.Now(),
|
||||
Parent: "",
|
||||
}
|
||||
childImage1 := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "child1",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
childImage2 := &Image{
|
||||
ID: GenerateID(),
|
||||
Comment: "child2",
|
||||
Created: time.Now(),
|
||||
Parent: parentImage.ID,
|
||||
}
|
||||
_ = graph.Register(nil, archive1, parentImage)
|
||||
_ = graph.Register(nil, archive2, childImage1)
|
||||
_ = graph.Register(nil, archive3, childImage2)
|
||||
|
||||
byParent, err := graph.ByParent()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
numChildren := len(byParent[parentImage.ID])
|
||||
if numChildren != 2 {
|
||||
t.Fatalf("Expected 2 children, found %d", numChildren)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNImages(graph *Graph, t *testing.T, n int) {
|
||||
if images, err := graph.All(); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user