diff options
Diffstat (limited to 'docs')
33 files changed, 2001 insertions, 0 deletions
diff --git a/docs/css/dark-mode.css b/docs/css/dark-mode.css new file mode 100644 index 0000000..bee053d --- /dev/null +++ b/docs/css/dark-mode.css @@ -0,0 +1,302 @@ +@media (prefers-color-scheme: dark) { + .md-main { + color: rgba(255, 255, 255, 0.75) !important; + background-color: #36393e !important; + } + + article img { + box-shadow: 0 0 1em #000; + } + + .md-main h1 { + color: rgba(255, 255, 255, 0.8) !important; + } + blockquote { + color: rgba(255, 255, 255, 0.75) !important; + } + table { + background-color: #616161 !important; + } + tbody { + background-color: #484848 !important; + } + .md-sidebar__scrollwrap::-webkit-scrollbar-thumb { + background-color: #e0e0e0 !important; + } + .md-nav { + color: rgba(255, 255, 255, 0.8) !important; + background-color: #36393e !important; + } + html .md-nav--primary .md-nav__title:before { + color: #fafafa !important; + } + .md-nav__title { + color: rgba(255, 255, 255, 0.9) !important; + background-color: #36393e !important; + } + .md-nav--primary .md-nav__link:after { + color: #fafafa !important; + } + .md-nav__list { + color: rgba(255, 255, 255, 0.8) !important; + background-color: #36393e !important; + } + .md-nav__item { + color: rgba(255, 255, 255, 0.7) !important; + background-color: #36393e !important; + } + .md-search__scrollwrap::-webkit-scrollbar-thumb { + background-color: #e0e0e0 !important; + } + .md-search__scrollwrap { + background-color: #44484e !important; + } + .md-search-result__article--document:before { + color: #eee !important; + } + .md-search-result__list { + color: #eee !important; + background-color: #36393e !important; + } + .md-search-result__meta { + background-color: #eee !important; + } + .md-search-result__teaser { + color: #bdbdbd !important; + } + .md-typeset code { + color: white !important; +/* box-shadow: 0.29412em 0 0 hsla(0, 0%, 100%, 0.07), + -0.29412em 0 0 hsla(0, 0%, 100%, 0.1);*/ + } + .md-typeset a code { + color: #94acff !important; + } + .md-typeset a:hover code { + text-decoration: underline; + } + .linenos { + color: #f5f5f5 !important; + background-color: #313131 !important; + } + .codehilite { + background-color: #44484e !important; + } + .md-typeset .codehilite::-webkit-scrollbar { + height: 1rem !important; + } + .codehilite pre { + color: #fafafa !important; + background-color: transparent !important; + } + .codehilite .hll { + background-color: #272822 !important; + } + .codehilite .c { + color: #8a8f98 !important; + } + .codehilite .err { + color: #960050 !important; + background-color: #1e0010 !important; + } + .codehilite .k { + color: #66d9ef !important; + } + .codehilite .l { + color: #ae81ff !important; + } + .codehilite .n { + color: #f8f8f2 !important; + } + .codehilite .o { + color: #f92672 !important; + } + .codehilite .p { + color: #f8f8f2 !important; + } + .codehilite .cm { + color: #8a8f98 !important; + } + .codehilite .cp { + color: #8a8f98 !important; + } + .codehilite .c1 { + color: #8a8f98 !important; + } + .codehilite .cs { + color: #8a8f98 !important; + } + .codehilite .ge { + font-style: italic !important; + } + .codehilite .gs { + font-weight: bold !important; + } + .codehilite .kc { + color: #66d9ef !important; + } + .codehilite .kd { + color: #66d9ef !important; + } + .codehilite .kn { + color: #f92672 !important; + } + .codehilite .kp { + color: #66d9ef !important; + } + .codehilite .kr { + color: #66d9ef !important; + } + .codehilite .kt { + color: #66d9ef !important; + } + .codehilite .ld { + color: #e6db74 !important; + } + .codehilite .m { + color: #ae81ff !important; + } + .codehilite .s { + color: #e6db74 !important; + } + .codehilite .na { + color: #a6e22e !important; + } + .codehilite .nb { + color: #f8f8f2 !important; + } + .codehilite .nc { + color: #a6e22e !important; + } + .codehilite .no { + color: #66d9ef !important; + } + .codehilite .nd { + color: #a6e22e !important; + } + .codehilite .ni { + color: #f8f8f2 !important; + } + .codehilite .ne { + color: #a6e22e !important; + } + .codehilite .nf { + color: #a6e22e !important; + } + .codehilite .nl { + color: #f8f8f2 !important; + } + .codehilite .nn { + color: #f8f8f2 !important; + } + .codehilite .nx { + color: #a6e22e !important; + } + .codehilite .py { + color: #f8f8f2 !important; + } + .codehilite .nt { + color: #f92672 !important; + } + .codehilite .nv { + color: #f8f8f2 !important; + } + .codehilite .ow { + color: #f92672 !important; + } + .codehilite .w { + color: #f8f8f2 !important; + } + .codehilite .mf { + color: #ae81ff !important; + } + .codehilite .mh { + color: #ae81ff !important; + } + .codehilite .mi { + color: #ae81ff !important; + } + .codehilite .mo { + color: #ae81ff !important; + } + .codehilite .sb { + color: #e6db74 !important; + } + .codehilite .sc { + color: #e6db74 !important; + } + .codehilite .sd { + color: #e6db74 !important; + } + .codehilite .s2 { + color: #e6db74 !important; + } + .codehilite .se { + color: #ae81ff !important; + } + .codehilite .sh { + color: #e6db74 !important; + } + .codehilite .si { + color: #e6db74 !important; + } + .codehilite .sx { + color: #e6db74 !important; + } + .codehilite .sr { + color: #e6db74 !important; + } + .codehilite .s1 { + color: #e6db74 !important; + } + .codehilite .ss { + color: #e6db74 !important; + } + .codehilite .bp { + color: #f8f8f2 !important; + } + .codehilite .vc { + color: #f8f8f2 !important; + } + .codehilite .vg { + color: #f8f8f2 !important; + } + .codehilite .vi { + color: #f8f8f2 !important; + } + .codehilite .il { + color: #ae81ff !important; + } + .codehilite .gu { + color: #8a8f98 !important; + } + .codehilite .gd { + color: #9c1042 !important; + background-color: #eaa; + } + .codehilite .gi { + color: #364c0a !important; + background-color: #91e891; + } + .md-clipboard:before { + color: rgba(255, 255, 255, 0.31); + } + .codehilite:hover .md-clipboard:before, .md-typeset .highlight:hover .md-clipboard:before, pre:hover .md-clipboard:before { + color: rgba(255, 255, 255, 0.6); + } + .md-typeset summary:after { + color: rgba(255, 255, 255, 0.26); + } + .md-typeset .admonition.example > .admonition-title, .md-typeset .admonition.example > summary, .md-typeset details.example > .admonition-title, .md-typeset details.example > summary { + background-color: rgba(154, 109, 255, 0.21); + } + .md-nav__link[data-md-state='blur'] { + color: #aec0ff; + } + .md-typeset .footnote { + color: #888484 !important; + } + .md-typeset .footnote-ref:before { + border-color: #888484 !important; + } +} diff --git a/docs/css/styles.css b/docs/css/styles.css new file mode 100644 index 0000000..b44b61f --- /dev/null +++ b/docs/css/styles.css @@ -0,0 +1,53 @@ +.text-center { + text-align: center; +} + +article img { + border: 1px solid #eee; + box-shadow: 0 0 1em #ccc; + margin: 30px auto 10px; +} + +article img.emojione { + border: none; + box-shadow: none; + margin: 0; +} + +.md-footer-nav { + background-color: rgba(0,0,0,.57); +} + + +/* Docker Branding */ +.md-header { + background-color: #0d9cec !important; +} + +@font-face{ + font-family: "Geomanist"; + src: url("../fonts/hinted-Geomanist-Book.ttf") +} + +body { + font-family: "Open Sans", sans-serif; + font-size: 15px; + font-weight: normal; + font-style: normal; + font-stretch: normal; + line-height: 1.5; + letter-spacing: normal; + color: #577482; +} + +h1, h2, h3, h4, .md-footer-nav__inner, .md-header-nav__title, footer.md-footer { + font-family: Geomanist; +} + +.md-header-nav__title { + line-height: 2.9rem; +} + +.md-header-nav__button img { + width: 145px; +}
\ No newline at end of file diff --git a/docs/fonts/hinted-Geomanist-Book.ttf b/docs/fonts/hinted-Geomanist-Book.ttf Binary files differnew file mode 100644 index 0000000..9f6e84c --- /dev/null +++ b/docs/fonts/hinted-Geomanist-Book.ttf diff --git a/docs/images/docker-labs-logo.svg b/docs/images/docker-labs-logo.svg new file mode 100644 index 0000000..f6cfe99 --- /dev/null +++ b/docs/images/docker-labs-logo.svg @@ -0,0 +1,19 @@ +<svg width="145" height="26.000000000000004" xmlns="http://www.w3.org/2000/svg" class="dicon " preserveAspectRatio="xMidYMid meet"> + <g> + <title>background</title> + <rect fill="none" id="canvas_background" height="402" width="582" y="-1" x="-1"></rect> + </g> + <g> + <title>Layer 1</title> + <g id="svg_1"> + <g id="svg_2" fill-rule="evenodd" fill="#FFF"> + <path id="svg_3" d="m63.12,14.601c0.292,-0.29 0.633,-0.519 1.023,-0.687c0.389,-0.168 0.806,-0.252 1.25,-0.252c0.402,0 0.773,0.067 1.114,0.202c0.341,0.134 0.667,0.333 0.977,0.595c0.183,0.147 0.39,0.22 0.62,0.22c0.275,0 0.501,-0.091 0.68,-0.275a0.943,0.943 0 0 0 0.27,-0.687a0.932,0.932 0 0 0 -0.329,-0.724c-0.937,-0.83 -2.048,-1.245 -3.332,-1.245c-1.412,0 -2.617,0.5 -3.615,1.502c-0.998,1.002 -1.497,2.211 -1.497,3.628c0,1.417 0.499,2.626 1.497,3.628c0.998,1.001 2.203,1.502 3.615,1.502c1.278,0 2.39,-0.415 3.332,-1.245a0.968,0.968 0 0 0 0.302,-0.706a0.92,0.92 0 0 0 -0.95,-0.953a1.021,1.021 0 0 0 -0.602,0.202c-0.305,0.263 -0.627,0.46 -0.968,0.591c-0.34,0.131 -0.712,0.197 -1.114,0.197c-0.444,0 -0.861,-0.084 -1.25,-0.252a3.199,3.199 0 0 1 -1.963,-2.964a3.194,3.194 0 0 1 0.94,-2.277zm15.771,-2.267a1.055,1.055 0 0 0 -0.205,-0.307a0.893,0.893 0 0 0 -0.301,-0.206a0.951,0.951 0 0 0 -0.374,-0.073a0.926,0.926 0 0 0 -0.512,0.146l-5.46,3.564l0,-7.146a0.93,0.93 0 0 0 -0.278,-0.683a0.913,0.913 0 0 0 -0.67,-0.279a0.924,0.924 0 0 0 -0.68,0.28a0.929,0.929 0 0 0 -0.28,0.682l0,12.735c0,0.262 0.093,0.488 0.28,0.678c0.185,0.189 0.412,0.283 0.68,0.283a0.906,0.906 0 0 0 0.67,-0.283a0.935,0.935 0 0 0 0.279,-0.678l0,-3.308l1.114,-0.733l4.218,4.755a0.88,0.88 0 0 0 0.639,0.247a0.951,0.951 0 0 0 0.374,-0.073a0.902,0.902 0 0 0 0.301,-0.206c0.085,-0.088 0.154,-0.19 0.205,-0.307a0.885,0.885 0 0 0 0.078,-0.367a0.97,0.97 0 0 0 -0.265,-0.668l-3.925,-4.434l3.825,-2.492c0.244,-0.165 0.365,-0.418 0.365,-0.76a0.887,0.887 0 0 0 -0.078,-0.367zm-21.838,5.785a3.255,3.255 0 0 1 -1.702,1.718a3.08,3.08 0 0 1 -1.251,0.257c-0.45,0 -0.87,-0.086 -1.26,-0.257a3.225,3.225 0 0 1 -1.013,-0.691a3.284,3.284 0 0 1 -0.68,-1.022a3.128,3.128 0 0 1 -0.252,-1.246c0,-0.44 0.084,-0.855 0.251,-1.246c0.168,-0.39 0.395,-0.731 0.68,-1.022c0.286,-0.29 0.624,-0.52 1.014,-0.691c0.39,-0.171 0.81,-0.257 1.26,-0.257c0.444,0 0.86,0.086 1.25,0.257a3.257,3.257 0 0 1 1.703,1.717c0.168,0.388 0.251,0.802 0.251,1.242c0,0.44 -0.083,0.854 -0.251,1.241zm0.662,-4.869c-1.01,-1.002 -2.215,-1.502 -3.615,-1.502c-1.412,0 -2.617,0.5 -3.615,1.502c-0.998,1.002 -1.498,2.211 -1.498,3.628c0,1.417 0.5,2.626 1.498,3.628c0.998,1.001 2.203,1.502 3.615,1.502c1.4,0 2.605,-0.5 3.615,-1.502c0.998,-0.99 1.497,-2.199 1.497,-3.628a5.3,5.3 0 0 0 -0.379,-1.97a5.031,5.031 0 0 0 -1.118,-1.658zm41.03,-0.861a1.797,1.797 0 0 0 -0.644,-0.39a3.775,3.775 0 0 0 -0.85,-0.197a7.268,7.268 0 0 0 -0.862,-0.054a4.97,4.97 0 0 0 -1.716,0.293a5.234,5.234 0 0 0 -1.489,0.842l0,-0.183a0.92,0.92 0 0 0 -0.278,-0.673a0.913,0.913 0 0 0 -0.671,-0.28a0.923,0.923 0 0 0 -0.68,0.28a0.92,0.92 0 0 0 -0.279,0.673l0,8.355a0.92,0.92 0 0 0 0.279,0.674c0.185,0.186 0.412,0.28 0.68,0.28a0.914,0.914 0 0 0 0.671,-0.28a0.92,0.92 0 0 0 0.278,-0.674l0,-4.177a3.232,3.232 0 0 1 0.936,-2.277c0.29,-0.29 0.629,-0.519 1.018,-0.687c0.39,-0.168 0.807,-0.252 1.25,-0.252c0.451,0 0.868,0.077 1.252,0.23c0.152,0.067 0.286,0.1 0.401,0.1a0.95,0.95 0 0 0 0.375,-0.073a0.89,0.89 0 0 0 0.3,-0.207c0.086,-0.088 0.154,-0.19 0.206,-0.306a0.913,0.913 0 0 0 0.078,-0.376a0.853,0.853 0 0 0 -0.256,-0.641l0.001,0zm-16.708,3.536c0.097,-0.336 0.247,-0.643 0.448,-0.92c0.2,-0.278 0.438,-0.516 0.711,-0.715c0.274,-0.199 0.576,-0.353 0.904,-0.463a3.175,3.175 0 0 1 2.023,0a3.279,3.279 0 0 1 1.606,1.177c0.204,0.278 0.358,0.585 0.461,0.921l-6.153,0zm6.692,-2.675c-1.01,-1.002 -2.216,-1.502 -3.615,-1.502c-1.412,0 -2.618,0.5 -3.616,1.502c-0.998,1.002 -1.497,2.211 -1.497,3.628c0,1.417 0.5,2.626 1.497,3.628c0.998,1.001 2.204,1.502 3.616,1.502c1.284,0 2.398,-0.415 3.341,-1.245a0.954,0.954 0 0 0 0.274,-0.688a0.927,0.927 0 0 0 -0.27,-0.682a0.918,0.918 0 0 0 -0.68,-0.27a0.995,0.995 0 0 0 -0.63,0.238a3.011,3.011 0 0 1 -0.93,0.55a3.202,3.202 0 0 1 -1.105,0.183c-0.353,0 -0.693,-0.055 -1.018,-0.165a3.28,3.28 0 0 1 -0.895,-0.463a3.197,3.197 0 0 1 -1.164,-1.635l7.23,0a0.94,0.94 0 0 0 0.959,-0.953c0,-0.708 -0.125,-1.367 -0.374,-1.974a4.991,4.991 0 0 0 -1.123,-1.654zm-42.988,4.87a3.245,3.245 0 0 1 -1.703,1.718c-0.389,0.17 -0.806,0.256 -1.25,0.256c-0.45,0 -0.87,-0.086 -1.26,-0.257a3.227,3.227 0 0 1 -1.013,-0.691a3.272,3.272 0 0 1 -0.68,-1.022a3.134,3.134 0 0 1 -0.251,-1.246c0,-0.44 0.083,-0.855 0.25,-1.246c0.168,-0.39 0.395,-0.731 0.68,-1.022c0.287,-0.29 0.624,-0.52 1.014,-0.691c0.39,-0.171 0.81,-0.257 1.26,-0.257c0.444,0 0.861,0.086 1.25,0.257a3.246,3.246 0 0 1 1.703,1.717c0.168,0.388 0.251,0.802 0.251,1.242a3.1,3.1 0 0 1 -0.25,1.241l-0.001,0.001zm1.2,-10.77a0.922,0.922 0 0 0 -0.949,0.953l0,4.571c-0.925,-0.751 -1.993,-1.126 -3.204,-1.126c-1.412,0 -2.617,0.5 -3.615,1.502c-0.999,1.002 -1.497,2.211 -1.497,3.628c0,1.417 0.498,2.626 1.497,3.628c0.998,1.001 2.203,1.502 3.615,1.502c1.4,0 2.605,-0.5 3.615,-1.502c0.999,-0.99 1.498,-2.199 1.498,-3.628l0,-8.575a0.94,0.94 0 0 0 -0.959,-0.953l-0.001,0zm-26.46,4.136l3.74,0l0,-3.378l-3.74,0l0,3.378zm-4.419,0l3.74,0l0,-3.378l-3.74,0l0,3.378zm-4.418,0l3.739,0l0,-3.378l-3.74,0l0,3.378l0.001,0zm-4.42,0l3.74,0l0,-3.378l-3.74,0l0,3.378zm-4.418,0l3.739,0l0,-3.378l-3.739,0l0,3.378zm4.419,-4.054l3.739,0l0,-3.378l-3.74,0l0,3.378l0.001,0zm4.419,0l3.739,0l0,-3.378l-3.74,0l0,3.378l0.001,0zm4.418,0l3.74,0l0,-3.378l-3.74,0l0,3.378zm0,-4.054l3.74,0l0,-3.378l-3.74,0l0,3.378zm15.258,5.668c-0.186,-1.352 -0.944,-2.524 -2.323,-3.584l-0.792,-0.525l-0.53,0.789c-0.675,1.014 -1.015,2.42 -0.903,3.769c0.05,0.474 0.207,1.323 0.698,2.069c-0.49,0.262 -1.456,0.623 -2.739,0.598l-24.591,0l-0.049,0.282c-0.23,1.355 -0.226,5.583 2.537,8.833c2.099,2.47 5.247,3.723 9.356,3.723c8.906,0 15.495,-4.075 18.58,-11.482c1.213,0.024 3.827,0.007 5.17,-2.541c0.034,-0.058 0.115,-0.212 0.349,-0.695l0.129,-0.264l-0.755,-0.501c-0.817,-0.543 -2.693,-0.742 -4.137,-0.471z"></path> + <g font-weight="normal" font-size="18" font-family="Geomanist Book ,sans-serif" opacity="0.5" fill-rule="evenodd" fill="none" id="Page-1"> + <text fill="#FFFFFF" id="labs"> + <tspan id="svg_4" y="22" x="101">Labs</tspan> + </text> + </g> + </g> + </g> + </g> + </svg>
\ No newline at end of file diff --git a/docs/images/pwd-badge.png b/docs/images/pwd-badge.png Binary files differnew file mode 100644 index 0000000..278ea96 --- /dev/null +++ b/docs/images/pwd-badge.png diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..6d9c155 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,2 @@ +redirect: /tutorial/ + diff --git a/docs/tutorial/image-building-best-practices/hvs.png b/docs/tutorial/image-building-best-practices/hvs.png Binary files differnew file mode 100644 index 0000000..bce851b --- /dev/null +++ b/docs/tutorial/image-building-best-practices/hvs.png diff --git a/docs/tutorial/image-building-best-practices/index.md b/docs/tutorial/image-building-best-practices/index.md new file mode 100644 index 0000000..49b51ca --- /dev/null +++ b/docs/tutorial/image-building-best-practices/index.md @@ -0,0 +1,270 @@ +## Security Scanning + +When you have built an image, it is good practice to scan it for security vulnerabilities using the `docker scan` command. +Docker has partnered with [Snyk](http://snyk.io) to provide the vulnerability scanning service. + +For example, to scan the `getting-started` image you created earlier in the tutorial, you can just type + +```bash +docker scan getting-started +``` + +The scan uses a constantly updated database of vulnerabilities, so the output you see will vary as new +vulnerabilities are discovered, but it might look something like this: + +```plaintext +✗ Low severity vulnerability found in freetype/freetype + Description: CVE-2020-15999 + Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641 + Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2 + From: freetype/freetype@2.10.0-r0 + From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0 + Fixed in: 2.10.0-r1 + +✗ Medium severity vulnerability found in libxml2/libxml2 + Description: Out-of-bounds Read + Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791 + Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1 + From: libxml2/libxml2@2.9.9-r3 + From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3 + From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3 + Fixed in: 2.9.9-r4 +``` + +The output lists the type of vulnerability, a URL to learn more, and importantly which version of the relevant library +fixes the vulnerability. + +There are several other options, which you can read about in the [docker scan documentation](https://docs.docker.com/engine/scan/). + +As well as scanning your newly built image on the command line, you can also [configure Docker Hub](https://docs.docker.com/docker-hub/vulnerability-scanning/) +to scan all newly pushed images automatically, and you can then see the results in both Docker Hub and Docker Desktop. + +{: style=width:75% } +{: .text-center } + +## Image Layering + +Did you know that you can look at what makes up an image? Using the `docker image history` +command, you can see the command that was used to create each layer within an image. + +1. Use the `docker image history` command to see the layers in the `getting-started` image you + created earlier in the tutorial. + + ```bash + docker image history getting-started + ``` + + You should get output that looks something like this (dates/IDs may be different). + + ```plaintext + IMAGE CREATED CREATED BY SIZE COMMENT + a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B + f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB + a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB + 9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B + b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B + <missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B + <missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B + <missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB + <missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B + <missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB + <missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B + <missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B + <missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MB + ``` + + Each of the lines represents a layer in the image. The display here shows the base at the bottom with + the newest layer at the top. Using this, you can also quickly see the size of each layer, helping + diagnose large images. + +1. You'll notice that several of the lines are truncated. If you add the `--no-trunc` flag, you'll get the + full output (yes... funny how you use a truncated flag to get untruncated output, huh?) + + ```bash + docker image history --no-trunc getting-started + ``` + + +## Layer Caching + +Now that you've seen the layering in action, there's an important lesson to learn to help decrease build +times for your container images. + +> Once a layer changes, all downstream layers have to be recreated as well + +Let's look at the Dockerfile we were using one more time... + +```dockerfile +FROM node:12-alpine +WORKDIR /app +COPY . . +RUN yarn install --production +CMD ["node", "src/index.js"] +``` + +Going back to the image history output, we see that each command in the Dockerfile becomes a new layer in the image. +You might remember that when we made a change to the image, the yarn dependencies had to be reinstalled. Is there a +way to fix this? It doesn't make much sense to ship around the same dependencies every time we build, right? + +To fix this, we need to restructure our Dockerfile to help support the caching of the dependencies. For Node-based +applications, those dependencies are defined in the `package.json` file. So, what if we copied only that file in first, +install the dependencies, and _then_ copy in everything else? Then, we only recreate the yarn dependencies if there was +a change to the `package.json`. Make sense? + +1. Update the Dockerfile to copy in the `package.json` first, install dependencies, and then copy everything else in. + + ```dockerfile hl_lines="3 4 5" + FROM node:12-alpine + WORKDIR /app + COPY package.json yarn.lock ./ + RUN yarn install --production + COPY . . + CMD ["node", "src/index.js"] + ``` + +1. Create a file named `.dockerignore` in the same folder as the Dockerfile with the following contents. + + ```ignore + node_modules + ``` + + `.dockerignore` files are an easy way to selectively copy only image relevant files. + You can read more about this + [here](https://docs.docker.com/engine/reference/builder/#dockerignore-file). + In this case, the `node_modules` folder should be omitted in the second `COPY` step because otherwise, + it would possibly overwrite files which were created by the command in the `RUN` step. + For further details on why this is recommended for Node.js applications and other best practices, + have a look at their guide on + [Dockerizing a Node.js web app](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/). + +1. Build a new image using `docker build`. + + ```bash + docker build -t getting-started . + ``` + + You should see output like this... + + ```plaintext + Sending build context to Docker daemon 219.1kB + Step 1/6 : FROM node:12-alpine + ---> b0dc3a5e5e9e + Step 2/6 : WORKDIR /app + ---> Using cache + ---> 9577ae713121 + Step 3/6 : COPY package.json yarn.lock ./ + ---> bd5306f49fc8 + Step 4/6 : RUN yarn install --production + ---> Running in d53a06c9e4c2 + yarn install v1.17.3 + [1/4] Resolving packages... + [2/4] Fetching packages... + info fsevents@1.2.9: The platform "linux" is incompatible with this module. + info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. + [3/4] Linking dependencies... + [4/4] Building fresh packages... + Done in 10.89s. + Removing intermediate container d53a06c9e4c2 + ---> 4e68fbc2d704 + Step 5/6 : COPY . . + ---> a239a11f68d8 + Step 6/6 : CMD ["node", "src/index.js"] + ---> Running in 49999f68df8f + Removing intermediate container 49999f68df8f + ---> e709c03bc597 + Successfully built e709c03bc597 + Successfully tagged getting-started:latest + ``` + + You'll see that all layers were rebuilt. Perfectly fine since we changed the Dockerfile quite a bit. + +1. Now, make a change to the `src/static/index.html` file (like change the `<title>` to say "The Awesome Todo App"). + +1. Build the Docker image now using `docker build -t getting-started .` again. This time, your output should look a little different. + + ```plaintext hl_lines="5 8 11" + Sending build context to Docker daemon 219.1kB + Step 1/6 : FROM node:12-alpine + ---> b0dc3a5e5e9e + Step 2/6 : WORKDIR /app + ---> Using cache + ---> 9577ae713121 + Step 3/6 : COPY package.json yarn.lock ./ + ---> Using cache + ---> bd5306f49fc8 + Step 4/6 : RUN yarn install --production + ---> Using cache + ---> 4e68fbc2d704 + Step 5/6 : COPY . . + ---> cccde25a3d9a + Step 6/6 : CMD ["node", "src/index.js"] + ---> Running in 2be75662c150 + Removing intermediate container 2be75662c150 + ---> 458e5c6f080c + Successfully built 458e5c6f080c + Successfully tagged getting-started:latest + ``` + + First off, you should notice that the build was MUCH faster! And, you'll see that steps 1-4 all have + `Using cache`. So, hooray! We're using the build cache. Pushing and pulling this image and updates to it + will be much faster as well. Hooray! + + +## Multi-Stage Builds + +While we're not going to dive into it too much in this tutorial, multi-stage builds are an incredibly powerful +tool to help use multiple stages to create an image. There are several advantages for them: + +- Separate build-time dependencies from runtime dependencies +- Reduce overall image size by shipping _only_ what your app needs to run + +### Maven/Tomcat Example + +When building Java-based applications, a JDK is needed to compile the source code to Java bytecode. However, +that JDK isn't needed in production. Also, you might be using tools like Maven or Gradle to help build the app. +Those also aren't needed in our final image. Multi-stage builds help. + +```dockerfile +FROM maven AS build +WORKDIR /app +COPY . . +RUN mvn package + +FROM tomcat +COPY --from=build /app/target/file.war /usr/local/tomcat/webapps +``` + +In this example, we use one stage (called `build`) to perform the actual Java build using Maven. In the second +stage (starting at `FROM tomcat`), we copy in files from the `build` stage. The final image is only the last stage +being created (which can be overridden using the `--target` flag). + + +### React Example + +When building React applications, we need a Node environment to compile the JS code (typically JSX), SASS stylesheets, +and more into static HTML, JS, and CSS. If we aren't doing server-side rendering, we don't even need a Node environment +for our production build. Why not ship the static resources in a static nginx container? + +```dockerfile +FROM node:12 AS build +WORKDIR /app +COPY package* yarn.lock ./ +RUN yarn install +COPY public ./public +COPY src ./src +RUN yarn run build + +FROM nginx:alpine +COPY --from=build /app/build /usr/share/nginx/html +``` + +Here, we are using a `node:12` image to perform the build (maximizing layer caching) and then copying the output +into an nginx container. Cool, huh? + + +## Recap + +By understanding a little bit about how images are structured, we can build images faster and ship fewer changes. +Scanning images gives us confidence that the containers we are running and distributing are secure. +Multi-stage builds also help us reduce overall image size and increase final container security by separating +build-time dependencies from runtime dependencies. diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md new file mode 100644 index 0000000..863ba6d --- /dev/null +++ b/docs/tutorial/index.md @@ -0,0 +1,72 @@ +--- +next_page: app.md +--- + +## The command you just ran + +Congratulations! You have started the container for this tutorial! +Let's first explain the command that you just ran. In case you forgot, +here's the command: + +```cli +docker run -d -p 80:80 docker/getting-started +``` + +You'll notice a few flags being used. Here's some more info on them: + +- `-d` - run the container in detached mode (in the background) +- `-p 80:80` - map port 80 of the host to port 80 in the container +- `docker/getting-started` - the image to use + +!!! info "Pro tip" + You can combine single character flags to shorten the full command. + As an example, the command above could be written as: + ``` + docker run -dp 80:80 docker/getting-started + ``` + +## The Docker Dashboard + +Before going too far, we want to highlight the Docker Dashboard, which gives +you a quick view of the containers running on your machine. It gives you quick +access to container logs, lets you get a shell inside the container, and lets you +easily manage container lifecycle (stop, remove, etc.). + +To access the dashboard, follow the instructions in the +[Docker Desktop manual](https://docs.docker.com/desktop/). If you open the dashboard +now, you will see this tutorial running! The container name (`jolly_bouman` below) is a +randomly created name. So, you'll most likely have a different name. + + + + +## What is a container? + +Now that you've run a container, what _is_ a container? Simply put, a container is +simply another process on your machine that has been isolated from all other processes +on the host machine. That isolation leverages [kernel namespaces and cgroups](https://medium.com/@saschagrunert/demystifying-containers-part-i-kernel-space-2c53d6979504), features that have been +in Linux for a long time. Docker has worked to make these capabilities approachable and easy to use. + +!!! info "Creating Containers from Scratch" + If you'd like to see how containers are built from scratch, Liz Rice from Aqua Security + has a fantastic talk in which she creates a container from scratch in Go. While she makes + a simple container, this talk doesn't go into networking, using images for the filesystem, + and more. But, it gives a _fantastic_ deep dive into how things are working. + + <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/8fi7uSYlOdc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> + +## What is a container image? + +When running a container, it uses an isolated filesystem. This custom filesystem is provided +by a **container image**. Since the image contains the container's filesystem, it must contain everything +needed to run an application - all dependencies, configuration, scripts, binaries, etc. The +image also contains other configuration for the container, such as environment variables, +a default command to run, and other metadata. + +We'll dive deeper into images later on, covering topics such as layering, best practices, and more. + +!!! info + If you're familiar with `chroot`, think of a container as an extended version of `chroot`. The + filesystem is simply coming from the image. But, a container adds additional isolation not + available when simply using chroot. + diff --git a/docs/tutorial/multi-container-apps/dashboard-multi-container-app.png b/docs/tutorial/multi-container-apps/dashboard-multi-container-app.png Binary files differnew file mode 100644 index 0000000..89fba77 --- /dev/null +++ b/docs/tutorial/multi-container-apps/dashboard-multi-container-app.png diff --git a/docs/tutorial/multi-container-apps/index.md b/docs/tutorial/multi-container-apps/index.md new file mode 100644 index 0000000..0e4026d --- /dev/null +++ b/docs/tutorial/multi-container-apps/index.md @@ -0,0 +1,290 @@ + +Up to this point, we have been working with single container apps. But, we now want to add MySQL to the +application stack. The following question often arises - "Where will MySQL run? Install it in the same +container or run it separately?" In general, **each container should do one thing and do it well.** A few +reasons: + +- There's a good chance you'd have to scale APIs and front-ends differently than databases. +- Separate containers let you version and update versions in isolation. +- While you may use a container for the database locally, you may want to use a managed service + for the database in production. You don't want to ship your database engine with your app then. +- Running multiple processes will require a process manager (the container only starts one process), + which adds complexity to container startup/shutdown. + +And there are more reasons. So, we will update our application to work like this: + + +{: .text-center } + + +## Container Networking + +Remember that containers, by default, run in isolation and don't know anything about other processes +or containers on the same machine. So, how do we allow one container to talk to another? The answer is +**networking**. Now, you don't have to be a network engineer (hooray!). Simply remember this rule... + +> If two containers are on the same network, they can talk to each other. If they aren't, they can't. + + +## Starting MySQL + +There are two ways to put a container on a network: 1) Assign it at start or 2) connect an existing container. +For now, we will create the network first and attach the MySQL container at startup. + +1. Create the network. + + ```bash + docker network create todo-app + ``` + +1. Start a MySQL container and attach it to the network. We're also going to define a few environment variables that the + database will use to initialize the database (see the "Environment Variables" section in the [MySQL Docker Hub listing](https://hub.docker.com/_/mysql/)). + + ```bash + docker run -d \ + --network todo-app --network-alias mysql \ + -v todo-mysql-data:/var/lib/mysql \ + -e MYSQL_ROOT_PASSWORD=secret \ + -e MYSQL_DATABASE=todos \ + mysql:5.7 + ``` + + If you are using PowerShell then use this command. + + ```powershell + docker run -d ` + --network todo-app --network-alias mysql ` + -v todo-mysql-data:/var/lib/mysql ` + -e MYSQL_ROOT_PASSWORD=secret ` + -e MYSQL_DATABASE=todos ` + mysql:5.7 + ``` + + You'll also see we specified the `--network-alias` flag. We'll come back to that in just a moment. + + !!! info "Pro-tip" + You'll notice we're using a volume named `todo-mysql-data` here and mounting it at `/var/lib/mysql`, which is + where MySQL stores its data. However, we never ran a `docker volume create` command. Docker recognizes we want + to use a named volume and creates one automatically for us. + + !!! info "Troubleshooting" + If you see a `docker: no matching manifest` error, it's because you're trying to run the container in a different + architecture than amd64, which is the only supported architecture for the mysql image at the moment. To solve this + add the flag `--platform linux/amd64` in the previous command. So your new command should look like this: + + ```bash + docker run -d \ + --network todo-app --network-alias mysql --platform linux/amd64 \ + -v todo-mysql-data:/var/lib/mysql \ + -e MYSQL_ROOT_PASSWORD=secret \ + -e MYSQL_DATABASE=todos \ + mysql:5.7 + ``` + +1. To confirm we have the database up and running, connect to the database and verify it connects. + + ```bash + docker exec -it <mysql-container-id> mysql -p + ``` + + When the password prompt comes up, type in **secret**. In the MySQL shell, list the databases and verify + you see the `todos` database. + + ```cli + mysql> SHOW DATABASES; + ``` + + You should see output that looks like this: + + ```plaintext + +--------------------+ + | Database | + +--------------------+ + | information_schema | + | mysql | + | performance_schema | + | sys | + | todos | + +--------------------+ + 5 rows in set (0.00 sec) + ``` + + Hooray! We have our `todos` database and it's ready for us to use! + + To exit the sql terminal type `exit` in the terminal. + + +## Connecting to MySQL + +Now that we know MySQL is up and running, let's use it! But, the question is... how? If we run +another container on the same network, how do we find the container (remember each container has its own IP +address)? + +To figure it out, we're going to make use of the [nicolaka/netshoot](https://github.com/nicolaka/netshoot) container, +which ships with a _lot_ of tools that are useful for troubleshooting or debugging networking issues. + +1. Start a new container using the nicolaka/netshoot image. Make sure to connect it to the same network. + + ```bash + docker run -it --network todo-app nicolaka/netshoot + ``` + +1. Inside the container, we're going to use the `dig` command, which is a useful DNS tool. We're going to look up + the IP address for the hostname `mysql`. + + ```bash + dig mysql + ``` + + And you'll get an output like this... + + ```text + ; <<>> DiG 9.14.1 <<>> mysql + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + + ;; QUESTION SECTION: + ;mysql. IN A + + ;; ANSWER SECTION: + mysql. 600 IN A 172.23.0.2 + + ;; Query time: 0 msec + ;; SERVER: 127.0.0.11#53(127.0.0.11) + ;; WHEN: Tue Oct 01 23:47:24 UTC 2019 + ;; MSG SIZE rcvd: 44 + ``` + + In the "ANSWER SECTION", you will see an `A` record for `mysql` that resolves to `172.23.0.2` + (your IP address will most likely have a different value). While `mysql` isn't normally a valid hostname, + Docker was able to resolve it to the IP address of the container that had that network alias (remember the + `--network-alias` flag we used earlier?). + + What this means is... our app only simply needs to connect to a host named `mysql` and it'll talk to the + database! It doesn't get much simpler than that! + + +## Running our App with MySQL + +The todo app supports the setting of a few environment variables to specify MySQL connection settings. They are: + +- `MYSQL_HOST` - the hostname for the running MySQL server +- `MYSQL_USER` - the username to use for the connection +- `MYSQL_PASSWORD` - the password to use for the connection +- `MYSQL_DB` - the database to use once connected + +!!! warning Setting Connection Settings via Env Vars + While using env vars to set connection settings is generally ok for development, it is **HIGHLY DISCOURAGED** + when running applications in production. Diogo Monica, the former lead of security at Docker, + [wrote a fantastic blog post](https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/) + explaining why. + + A more secure mechanism is to use the secret support provided by your container orchestration framework. In most cases, + these secrets are mounted as files in the running container. You'll see many apps (including the MySQL image and the todo app) + also support env vars with a `_FILE` suffix to point to a file containing the variable. + + As an example, setting the `MYSQL_PASSWORD_FILE` var will cause the app to use the contents of the referenced file + as the connection password. Docker doesn't do anything to support these env vars. Your app will need to know to look for + the variable and get the file contents. + + +With all of that explained, let's start our dev-ready container! + +1. We'll specify each of the environment variables above, as well as connect the container to our app network. + + ```bash hl_lines="3 4 5 6 7" + docker run -dp 3000:3000 \ + -w /app -v "$(pwd):/app" \ + --network todo-app \ + -e MYSQL_HOST=mysql \ + -e MYSQL_USER=root \ + -e MYSQL_PASSWORD=secret \ + -e MYSQL_DB=todos \ + node:12-alpine \ + sh -c "yarn install && yarn run dev" + ``` + + If you updated your docker file in the Bind Mount section of the tutorial use the updated command: + + ```bash hl_lines="3 4 5 6 7" + docker run -dp 3000:3000 \ + -w /app -v "$(pwd):/app" \ + --network todo-app \ + -e MYSQL_HOST=mysql \ + -e MYSQL_USER=root \ + -e MYSQL_PASSWORD=secret \ + -e MYSQL_DB=todos \ + node:12-alpine \ + sh -c "apk --no-cache --virtual build-dependencies add python2 make g++ && yarn install && yarn run dev" + ``` + + If you are using PowerShell then use this command. + + ```powershell hl_lines="3 4 5 6 7" + docker run -dp 3000:3000 ` + -w /app -v "$(pwd):/app" ` + --network todo-app ` + -e MYSQL_HOST=mysql ` + -e MYSQL_USER=root ` + -e MYSQL_PASSWORD=secret ` + -e MYSQL_DB=todos ` + node:12-alpine ` + sh -c "yarn install && yarn run dev" + ``` + +1. If we look at the logs for the container (`docker logs <container-id>`), we should see a message indicating it's + using the mysql database. + + ```plaintext hl_lines="7" + # Previous log messages omitted + $ nodemon src/index.js + [nodemon] 1.19.2 + [nodemon] to restart at any time, enter `rs` + [nodemon] watching dir(s): *.* + [nodemon] starting `node src/index.js` + Connected to mysql db at host mysql + Listening on port 3000 + ``` + +1. Open the app in your browser and add a few items to your todo list. + +1. Connect to the mysql database and prove that the items are being written to the database. Remember, the password + is **secret**. + + ```bash + docker exec -it <mysql-container-id> mysql -p todos + ``` + + And in the mysql shell, run the following: + + ```plaintext + mysql> select * from todo_items; + +--------------------------------------+--------------------+-----------+ + | id | name | completed | + +--------------------------------------+--------------------+-----------+ + | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 | + | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 | + +--------------------------------------+--------------------+-----------+ + ``` + + Obviously, your table will look different because it has your items. But, you should see them stored there! + +If you take a quick look at the Docker Dashboard, you'll see that we have two app containers running. But, there's +no real indication that they are grouped together in a single app. We'll see how to make that better shortly! + + + +## Recap + +At this point, we have an application that now stores its data in an external database running in a separate +container. We learned a little bit about container networking and saw how service discovery can be performed +using DNS. + +But, there's a good chance you are starting to feel a little overwhelmed with everything you need to do to start up +this application. We have to create a network, start containers, specify all of the environment variables, expose +ports, and more! That's a lot to remember and it's certainly making things harder to pass along to someone else. + +In the next section, we'll talk about Docker Compose. With Docker Compose, we can share our application stacks in a +much easier way and let others spin them up with a single (and simple) command! diff --git a/docs/tutorial/multi-container-apps/multi-app-architecture.png b/docs/tutorial/multi-container-apps/multi-app-architecture.png Binary files differnew file mode 100644 index 0000000..463cd95 --- /dev/null +++ b/docs/tutorial/multi-container-apps/multi-app-architecture.png diff --git a/docs/tutorial/our-application/dashboard-two-containers.png b/docs/tutorial/our-application/dashboard-two-containers.png Binary files differnew file mode 100644 index 0000000..14fada7 --- /dev/null +++ b/docs/tutorial/our-application/dashboard-two-containers.png diff --git a/docs/tutorial/our-application/ide-screenshot.png b/docs/tutorial/our-application/ide-screenshot.png Binary files differnew file mode 100644 index 0000000..6b0468b --- /dev/null +++ b/docs/tutorial/our-application/ide-screenshot.png diff --git a/docs/tutorial/our-application/index.md b/docs/tutorial/our-application/index.md new file mode 100644 index 0000000..2814534 --- /dev/null +++ b/docs/tutorial/our-application/index.md @@ -0,0 +1,114 @@ + +For the rest of this tutorial, we will be working with a simple todo +list manager that is running in Node.js. If you're not familiar with Node.js, +don't worry! No real JavaScript experience is needed! + +At this point, your development team is quite small and you're simply +building an app to prove out your MVP (minimum viable product). You want +to show how it works and what it's capable of doing without needing to +think about how it will work for a large team, multiple developers, etc. + +{: style="width:50%;" } +{ .text-center } + +## Getting our App + +Before we can run the application, we need to get the application source code onto +our machine. For real projects, you will typically clone the repo. But, for this tutorial, +we have created a ZIP file containing the application. + +1. [Download the ZIP](/assets/app.zip). Open the ZIP file and make sure you extract the + contents. + +1. Once extracted, use your favorite code editor to open the project. If you're in need of + an editor, you can use [Visual Studio Code](https://code.visualstudio.com/). You should + see the `package.json` and two subdirectories (`src` and `spec`). + + {: style="width:650px;margin-top:20px;"} + {: .text-center } + +## Building the App's Container Image + +In order to build the application, we need to use a `Dockerfile`. A +Dockerfile is simply a text-based script of instructions that is used to +create a container image. If you've created Dockerfiles before, you might +see a few flaws in the Dockerfile below. But, don't worry! We'll go over them. + +1. Create a file named `Dockerfile` in the same folder as the file `package.json` with the following contents. + + ```dockerfile + FROM node:12-alpine + # Adding build tools to make yarn install work on Apple silicon / arm64 machines + RUN apk add --no-cache python2 g++ make + WORKDIR /app + COPY . . + RUN yarn install --production + CMD ["node", "src/index.js"] + ``` + + Please check that the file `Dockerfile` has no file extension like `.txt`. Some editors may append this file extension automatically and this would result in an error in the next step. + +1. If you haven't already done so, open a terminal and go to the `app` directory with the `Dockerfile`. Now build the container image using the `docker build` command. + + ```bash + docker build -t getting-started . + ``` + + This command used the Dockerfile to build a new container image. You might + have noticed that a lot of "layers" were downloaded. This is because we instructed + the builder that we wanted to start from the `node:12-alpine` image. But, since we + didn't have that on our machine, that image needed to be downloaded. + + After the image was downloaded, we copied in our application and used `yarn` to + install our application's dependencies. The `CMD` directive specifies the default + command to run when starting a container from this image. + + Finally, the `-t` flag tags our image. Think of this simply as a human-readable name + for the final image. Since we named the image `getting-started`, we can refer to that + image when we run a container. + + The `.` at the end of the `docker build` command tells that Docker should look for the `Dockerfile` in the current directory. + +## Starting an App Container + +Now that we have an image, let's run the application! To do so, we will use the `docker run` +command (remember that from earlier?). + +1. Start your container using the `docker run` command and specify the name of the image we + just created: + + ```bash + docker run -dp 3000:3000 getting-started + ``` + + Remember the `-d` and `-p` flags? We're running the new container in "detached" mode (in the + background) and creating a mapping between the host's port 3000 to the container's port 3000. + Without the port mapping, we wouldn't be able to access the application. + +1. After a few seconds, open your web browser to [http://localhost:3000](http://localhost:3000). + You should see our app! + + {: style="width:450px;margin-top:20px;"} + {: .text-center } + +1. Go ahead and add an item or two and see that it works as you expect. You can mark items as + complete and remove items. Your frontend is successfully storing items in the backend! + Pretty quick and easy, huh? + + +At this point, you should have a running todo list manager with a few items, all built by you! +Now, let's make a few changes and learn about managing our containers. + +If you take a quick look at the Docker Dashboard, you should see your two containers running now +(this tutorial and your freshly launched app container)! + + + + +## Recap + +In this short section, we learned the very basics about building a container image and created a +Dockerfile to do so. Once we built an image, we started the container and saw the running app! + +Next, we're going to make a modification to our app and learn how to update our running application +with a new image. Along the way, we'll learn a few other useful commands. diff --git a/docs/tutorial/our-application/todo-list-empty.png b/docs/tutorial/our-application/todo-list-empty.png Binary files differnew file mode 100644 index 0000000..81fa302 --- /dev/null +++ b/docs/tutorial/our-application/todo-list-empty.png diff --git a/docs/tutorial/our-application/todo-list-sample.png b/docs/tutorial/our-application/todo-list-sample.png Binary files differnew file mode 100644 index 0000000..681f2ad --- /dev/null +++ b/docs/tutorial/our-application/todo-list-sample.png diff --git a/docs/tutorial/persisting-our-data/dashboard-open-cli-ubuntu.png b/docs/tutorial/persisting-our-data/dashboard-open-cli-ubuntu.png Binary files differnew file mode 100644 index 0000000..6ca16d3 --- /dev/null +++ b/docs/tutorial/persisting-our-data/dashboard-open-cli-ubuntu.png diff --git a/docs/tutorial/persisting-our-data/index.md b/docs/tutorial/persisting-our-data/index.md new file mode 100644 index 0000000..19bdcfa --- /dev/null +++ b/docs/tutorial/persisting-our-data/index.md @@ -0,0 +1,161 @@ + +In case you didn't notice, our todo list is being wiped clean every single time +we launch the container. Why is this? Let's dive into how the container is working. + +## The Container's Filesystem + +When a container runs, it uses the various layers from an image for its filesystem. +Each container also gets its own "scratch space" to create/update/remove files. Any +changes won't be seen in another container, _even if_ they are using the same image. + +### Seeing this in Practice + +To see this in action, we're going to start two containers and create a file in each. +What you'll see is that the files created in one container aren't available in another. + +1. Start a `ubuntu` container that will create a file named `/data.txt` with a random number + between 1 and 10000. + + ```bash + docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null" + ``` + + In case you're curious about the command, we're starting a bash shell and invoking two + commands (why we have the `&&`). The first portion picks a single random number and writes + it to `/data.txt`. The second command is simply watching a file to keep the container running. + +1. Validate we can see the output by `exec`'ing into the container. To do so, open the Dashboard and click the first action of the container that is running the `ubuntu` image. + + {: style=width:75% } +{: .text-center } + + You will see a terminal that is running a shell in the ubuntu container. Run the following command to see the content of the `/data.txt` file. Close this terminal afterwards again. + + ```bash + cat /data.txt + ``` + + If you prefer the command line you can use the `docker exec` command to do the same. You need to get the + container's ID (use `docker ps` to get it) and get the content with the following command. + + ```bash + docker exec <container-id> cat /data.txt + ``` + + You should see a random number! + +1. Now, let's start another `ubuntu` container (the same image) and we'll see we don't have the same + file. + + ```bash + docker run -it ubuntu ls / + ``` + + And look! There's no `data.txt` file there! That's because it was written to the scratch space for + only the first container. + +1. Go ahead and remove the first container using the `docker rm -f <container-id>` command. + ```bash + docker rm -f <container-id> + ``` + +## Container Volumes + +With the previous experiment, we saw that each container starts from the image definition each time it starts. +While containers can create, update, and delete files, those changes are lost when the container is removed +and all changes are isolated to that container. With volumes, we can change all of this. + +[Volumes](https://docs.docker.com/storage/volumes/) provide the ability to connect specific filesystem paths of +the container back to the host machine. If a directory in the container is mounted, changes in that +directory are also seen on the host machine. If we mount that same directory across container restarts, we'd see +the same files. + +There are two main types of volumes. We will eventually use both, but we will start with **named volumes**. + +## Persisting our Todo Data + +By default, the todo app stores its data in a [SQLite Database](https://www.sqlite.org/index.html) at +`/etc/todos/todo.db`. If you're not familiar with SQLite, no worries! It's simply a relational database in +which all of the data is stored in a single file. While this isn't the best for large-scale applications, +it works for small demos. We'll talk about switching this to a different database engine later. + +With the database being a single file, if we can persist that file on the host and make it available to the +next container, it should be able to pick up where the last one left off. By creating a volume and attaching +(often called "mounting") it to the directory the data is stored in, we can persist the data. As our container +writes to the `todo.db` file, it will be persisted to the host in the volume. + +As mentioned, we are going to use a **named volume**. Think of a named volume as simply a bucket of data. +Docker maintains the physical location on the disk and you only need to remember the name of the volume. +Every time you use the volume, Docker will make sure the correct data is provided. + +1. Create a volume by using the `docker volume create` command. + + ```bash + docker volume create todo-db + ``` + +1. Stop the todo app container once again in the Dashboard (or with `docker rm -f <container-id>`), as it is still running without using the persistent volume. + +1. Start the todo app container, but add the `-v` flag to specify a volume mount. We will use the named volume and mount + it to `/etc/todos`, which will capture all files created at the path. + + ```bash + docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started + ``` + +1. Once the container starts up, open the app and add a few items to your todo list. + + {: style="width: 55%; " } + {: .text-center } + +1. Remove the container for the todo app. Use the Dashboard or `docker ps` to get the ID and then `docker rm -f <container-id>` to remove it. + +1. Start a new container using the same command from above. + +1. Open the app. You should see your items still in your list! + +1. Go ahead and remove the container when you're done checking out your list. + +Hooray! You've now learned how to persist data! + +!!! info "Pro-tip" + While named volumes and bind mounts (which we'll talk about in a minute) are the two main types of volumes supported + by a default Docker engine installation, there are many volume driver plugins available to support NFS, SFTP, NetApp, + and more! This will be especially important once you start running containers on multiple hosts in a clustered + environment with Swarm, Kubernetes, etc. + +## Diving into our Volume + +A lot of people frequently ask "Where is Docker _actually_ storing my data when I use a named volume?" If you want to know, +you can use the `docker volume inspect` command. + +```bash +docker volume inspect todo-db +[ + { + "CreatedAt": "2019-09-26T02:18:36Z", + "Driver": "local", + "Labels": {}, + "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", + "Name": "todo-db", + "Options": {}, + "Scope": "local" + } +] +``` + +The `Mountpoint` is the actual location on the disk where the data is stored. Note that on most machines, you will +need to have root access to access this directory from the host. But, that's where it is! + +!!! info "Accessing Volume data directly on Docker Desktop" + While running in Docker Desktop, the Docker commands are actually running inside a small VM on your machine. + If you wanted to look at the actual contents of the Mountpoint directory, you would need to first get inside + of the VM. + +## Recap + +At this point, we have a functioning application that can survive restarts! We can show it off to our investors and +hope they can catch our vision! + +However, we saw earlier that rebuilding images for every change takes quite a bit of time. There's got to be a better +way to make changes, right? With bind mounts (which we hinted at earlier), there is a better way! Let's take a look at that now! diff --git a/docs/tutorial/persisting-our-data/items-added.png b/docs/tutorial/persisting-our-data/items-added.png Binary files differnew file mode 100644 index 0000000..9cddfb5 --- /dev/null +++ b/docs/tutorial/persisting-our-data/items-added.png diff --git a/docs/tutorial/sharing-our-app/index.md b/docs/tutorial/sharing-our-app/index.md new file mode 100644 index 0000000..42a12f9 --- /dev/null +++ b/docs/tutorial/sharing-our-app/index.md @@ -0,0 +1,92 @@ + +Now that we've built an image, let's share it! To share Docker images, you have to use a Docker +registry. The default registry is Docker Hub and is where all of the images we've used have come from. + +## Create a Repo + +To push an image, we first need to create a repo on Docker Hub. + +1. Go to [Docker Hub](https://hub.docker.com) and log in if you need to. + +1. Click the **Create Repository** button. + +1. For the repo name, use `getting-started`. Make sure the Visibility is `Public`. + +1. Click the **Create** button! + +If you look on the right-side of the page, you'll see a section named **Docker commands**. This gives +an example command that you will need to run to push to this repo. + +{: style=width:75% } +{: .text-center } + +## Pushing our Image + +1. In the command line, try running the push command you see on Docker Hub. Note that your command + will be using your namespace, not "docker". + + ```plaintext + $ docker push docker/getting-started + The push refers to repository [docker.io/docker/getting-started] + An image does not exist locally with the tag: docker/getting-started + ``` + + Why did it fail? The push command was looking for an image named docker/getting-started, but + didn't find one. If you run `docker image ls`, you won't see one either. + + To fix this, we need to "tag" our existing image we've built to give it another name. + +1. Login to the Docker Hub using the command `docker login -u YOUR-USER-NAME`. + +1. Use the `docker tag` command to give the `getting-started` image a new name. Be sure to swap out + `YOUR-USER-NAME` with your Docker ID. + + ```bash + docker tag getting-started YOUR-USER-NAME/getting-started + ``` + +1. Now try your push command again. If you're copying the value from Docker Hub, you can drop the + `tagname` portion, as we didn't add a tag to the image name. If you don't specify a tag, Docker + will use a tag called `latest`. + + ```bash + docker push YOUR-USER-NAME/getting-started + ``` + +## Running our Image on a New Instance + +Now that our image has been built and pushed into a registry, let's try running our app on a brand +new instance that has never seen this container image! To do this, we will use Play with Docker. + +1. Open your browser to [Play with Docker](https://labs.play-with-docker.com/). + +1. Log in with your Docker Hub account. + +1. Once you're logged in, click on the "+ ADD NEW INSTANCE" link in the left side bar. (If you don't see it, make your browser a little wider.) After a few seconds, a terminal window will be opened in your browser. + + {: style=width:75% } +{: .text-center } + + +1. In the terminal, start your freshly pushed app. + + ```bash + docker run -dp 3000:3000 YOUR-USER-NAME/getting-started + ``` + + You should see the image get pulled down and eventually start up! + +1. Click on the 3000 badge when it comes up and you should see the app with your modifications! Hooray! + If the 3000 badge doesn't show up, you can click on the "Open Port" button and type in 3000. + +## Recap + +In this section, we learned how to share our images by pushing them to a registry. We then went to a +brand new instance and were able to run the freshly pushed image. This is quite common in CI pipelines, +where the pipeline will create the image and push it to a registry and then the production environment +can use the latest version of the image. + +Now that we have that figured out, let's circle back around to what we noticed at the end of the last +section. As a reminder, we noticed that when we restarted the app, we lost all of our todo list items. +That's obviously not a great user experience, so let's learn how we can persist the data across +restarts! diff --git a/docs/tutorial/sharing-our-app/push-command.png b/docs/tutorial/sharing-our-app/push-command.png Binary files differnew file mode 100644 index 0000000..0b0a2df --- /dev/null +++ b/docs/tutorial/sharing-our-app/push-command.png diff --git a/docs/tutorial/sharing-our-app/pwd-add-new-instance.png b/docs/tutorial/sharing-our-app/pwd-add-new-instance.png Binary files differnew file mode 100644 index 0000000..944e286 --- /dev/null +++ b/docs/tutorial/sharing-our-app/pwd-add-new-instance.png diff --git a/docs/tutorial/tutorial-in-dashboard.png b/docs/tutorial/tutorial-in-dashboard.png Binary files differnew file mode 100644 index 0000000..002cd05 --- /dev/null +++ b/docs/tutorial/tutorial-in-dashboard.png diff --git a/docs/tutorial/updating-our-app/dashboard-removing-container.png b/docs/tutorial/updating-our-app/dashboard-removing-container.png Binary files differnew file mode 100644 index 0000000..f9813ec --- /dev/null +++ b/docs/tutorial/updating-our-app/dashboard-removing-container.png diff --git a/docs/tutorial/updating-our-app/index.md b/docs/tutorial/updating-our-app/index.md new file mode 100644 index 0000000..92f752e --- /dev/null +++ b/docs/tutorial/updating-our-app/index.md @@ -0,0 +1,114 @@ + +As a small feature request, we've been asked by the product team to +change the "empty text" when we don't have any todo list items. They +would like to transition it to the following: + +> You have no todo items yet! Add one above! + +Pretty simple, right? Let's make the change. + +## Updating our Source Code + +1. In the `src/static/js/app.js` file, update line 56 to use the new empty text. + + ```diff + - <p className="text-center">No items yet! Add one above!</p> + + <p className="text-center">You have no todo items yet! Add one above!</p> + ``` + +1. Let's build our updated version of the image, using the same command we used before. + + ```bash + docker build -t getting-started . + ``` + +1. Let's start a new container using the updated code. + + ```bash + docker run -dp 3000:3000 getting-started + ``` + +**Uh oh!** You probably saw an error like this (the IDs will be different): + +```bash +docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell +(bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated. +``` + +So, what happened? We aren't able to start the new container because our old container is still +running. The reason this is a problem is because that container is using the host's port 3000 and +only one process on the machine (containers included) can listen to a specific port. To fix this, +we need to remove the old container. + + +## Replacing our Old Container + +To remove a container, it first needs to be stopped. Once it has stopped, it can be removed. We have two +ways that we can remove the old container. Feel free to choose the path that you're most comfortable with. + + +### Removing a container using the CLI + +1. Get the ID of the container by using the `docker ps` command. + + ```bash + docker ps + ``` + +1. Use the `docker stop` command to stop the container. + + ```bash + # Swap out <the-container-id> with the ID from docker ps + docker stop <the-container-id> + ``` + +1. Once the container has stopped, you can remove it by using the `docker rm` command. + + ```bash + docker rm <the-container-id> + ``` + +!!! info "Pro tip" + You can stop and remove a container in a single command by adding the "force" flag + to the `docker rm` command. For example: `docker rm -f <the-container-id>` + +### Removing a container using the Docker Dashboard + +If you open the Docker dashboard, you can remove a container with two clicks! It's certainly +much easier than having to look up the container ID and remove it. + +1. With the dashboard opened, hover over the app container and you'll see a collection of action + buttons appear on the right. + +1. Click on the trash can icon to delete the container. + +1. Confirm the removal and you're done! + + + + +### Starting our updated app container + +1. Now, start your updated app. + + ```bash + docker run -dp 3000:3000 getting-started + ``` + +1. Refresh your browser on [http://localhost:3000](http://localhost:3000) and you should see your updated help text! + +{: style="width:55%" } +{: .text-center } + + + +## Recap + +While we were able to build an update, there were two things you might have noticed: + +- All of the existing items in our todo list are gone! That's not a very good app! We'll talk about that +shortly. +- There were _a lot_ of steps involved for such a small change. In an upcoming section, we'll talk about +how to see code updates without needing to rebuild and start a new container every time we make a change. + +Before talking about persistence, we'll quickly see how to share these images with others. diff --git a/docs/tutorial/updating-our-app/todo-list-updated-empty-text.png b/docs/tutorial/updating-our-app/todo-list-updated-empty-text.png Binary files differnew file mode 100644 index 0000000..7017b68 --- /dev/null +++ b/docs/tutorial/updating-our-app/todo-list-updated-empty-text.png diff --git a/docs/tutorial/using-bind-mounts/index.md b/docs/tutorial/using-bind-mounts/index.md new file mode 100644 index 0000000..c4b61ae --- /dev/null +++ b/docs/tutorial/using-bind-mounts/index.md @@ -0,0 +1,127 @@ + +In the previous chapter, we talked about and used a **named volume** to persist the data in our database. +Named volumes are great if we simply want to store data, as we don't have to worry about _where_ the data +is stored. + +With **bind mounts**, we control the exact mountpoint on the host. We can use this to persist data, but is often +used to provide additional data into containers. When working on an application, we can use a bind mount to +mount our source code into the container to let it see code changes, respond, and let us see the changes right +away. + +For Node-based applications, [nodemon](https://npmjs.com/package/nodemon) is a great tool to watch for file +changes and then restart the application. There are equivalent tools in most other languages and frameworks. + +## Quick Volume Type Comparisons + +Bind mounts and named volumes are the two main types of volumes that come with the Docker engine. However, additional +volume drivers are available to support other use cases ([SFTP](https://github.com/vieux/docker-volume-sshfs), [Ceph](https://ceph.com/geen-categorie/getting-started-with-the-docker-rbd-volume-plugin/), [NetApp](https://netappdvp.readthedocs.io/en/stable/), [S3](https://github.com/elementar/docker-s3-volume), and more). + +| | Named Volumes | Bind Mounts | +| - | ------------- | ----------- | +| Host Location | Docker chooses | You control | +| Mount Example (using `-v`) | my-volume:/usr/local/data | /path/to/data:/usr/local/data | +| Populates new volume with container contents | Yes | No | +| Supports Volume Drivers | Yes | No | + + +## Starting a Dev-Mode Container + +To run our container to support a development workflow, we will do the following: + +- Mount our source code into the container +- Install all dependencies, including the "dev" dependencies +- Start nodemon to watch for filesystem changes + +So, let's do it! + +1. Make sure you don't have any previous `getting-started` containers running. + +1. Also make sure you are in app source code directory, i.e. `/path/to/getting-started/app`. If you aren't, you can `cd` into it, .e.g: + + ```bash + cd /path/to/getting-started/app + ``` + +1. Now that you are in the `getting-started/app` directory, run the following command. We'll explain what's going on afterwards: + + ```bash + docker run -dp 3000:3000 \ + -w /app -v "$(pwd):/app" \ + node:12-alpine \ + sh -c "yarn install && yarn run dev" + ``` + + If you are using PowerShell then use this command. + + ```powershell + docker run -dp 3000:3000 ` + -w /app -v "$(pwd):/app" ` + node:12-alpine ` + sh -c "yarn install && yarn run dev" + ``` + + If you are using an Apple Silicon Mac or another ARM64 device then use this command. + + ```bash + docker run -dp 3000:3000 \ + -w /app -v "$(pwd):/app" \ + node:12-alpine \ + sh -c "apk add --no-cache python2 g++ make && yarn install && yarn run dev" + ``` + + - `-dp 3000:3000` - same as before. Run in detached (background) mode and create a port mapping + - `-w /app` - sets the container's present working directory where the command will run from + - `-v "$(pwd):/app"` - bind mount (link) the host's present `getting-started/app` directory to the container's `/app` directory. Note: Docker requires absolute paths for binding mounts, so in this example we use `pwd` for printing the absolute path of the working directory, i.e. the `app` directory, instead of typing it manually + - `node:12-alpine` - the image to use. Note that this is the base image for our app from the Dockerfile + - `sh -c "yarn install && yarn run dev"` - the command. We're starting a shell using `sh` (alpine doesn't have `bash`) and + running `yarn install` to install _all_ dependencies and then running `yarn run dev`. If we look in the `package.json`, + we'll see that the `dev` script is starting `nodemon`. + +1. You can watch the logs using `docker logs -f <container-id>`. You'll know you're ready to go when you see this... + + ```bash + docker logs -f <container-id> + $ nodemon src/index.js + [nodemon] 1.19.2 + [nodemon] to restart at any time, enter `rs` + [nodemon] watching dir(s): *.* + [nodemon] starting `node src/index.js` + Using sqlite database at /etc/todos/todo.db + Listening on port 3000 + ``` + + When you're done watching the logs, exit out by hitting `Ctrl`+`C`. + +1. Now, let's make a change to the app. In the `src/static/js/app.js` file, let's change the "Add Item" button to simply say + "Add". This change will be on line 109 - remember to save the file. + + ```diff + - {submitting ? 'Adding...' : 'Add Item'} + + {submitting ? 'Adding...' : 'Add'} + ``` + +1. Simply refresh the page (or open it) and you should see the change reflected in the browser almost immediately. It might + take a few seconds for the Node server to restart, so if you get an error, just try refreshing after a few seconds. + + {: style="width:75%;"} + {: .text-center } + +1. Feel free to make any other changes you'd like to make. When you're done, stop the container and build your new image + using `docker build -t getting-started .`. + + +Using bind mounts is _very_ common for local development setups. The advantage is that the dev machine doesn't need to have +all of the build tools and environments installed. With a single `docker run` command, the dev environment is pulled and ready +to go. We'll talk about Docker Compose in a future step, as this will help simplify our commands (we're already getting a lot +of flags). + +## Recap + +At this point, we can persist our database and respond rapidly to the needs and demands of our investors and founders. Hooray! +But, guess what? We received great news! + +**Your project has been selected for future development!** + +In order to prepare for production, we need to migrate our database from working in SQLite to something that can scale a +little better. For simplicity, we'll keep with a relational database and switch our application to use MySQL. But, how +should we run MySQL? How do we allow the containers to talk to each other? We'll talk about that next! diff --git a/docs/tutorial/using-bind-mounts/updated-add-button.png b/docs/tutorial/using-bind-mounts/updated-add-button.png Binary files differnew file mode 100644 index 0000000..cce7a05 --- /dev/null +++ b/docs/tutorial/using-bind-mounts/updated-add-button.png diff --git a/docs/tutorial/using-docker-compose/dashboard-app-project-collapsed.png b/docs/tutorial/using-docker-compose/dashboard-app-project-collapsed.png Binary files differnew file mode 100644 index 0000000..6584b03 --- /dev/null +++ b/docs/tutorial/using-docker-compose/dashboard-app-project-collapsed.png diff --git a/docs/tutorial/using-docker-compose/dashboard-app-project-expanded.png b/docs/tutorial/using-docker-compose/dashboard-app-project-expanded.png Binary files differnew file mode 100644 index 0000000..a51a2b8 --- /dev/null +++ b/docs/tutorial/using-docker-compose/dashboard-app-project-expanded.png diff --git a/docs/tutorial/using-docker-compose/index.md b/docs/tutorial/using-docker-compose/index.md new file mode 100644 index 0000000..d2097db --- /dev/null +++ b/docs/tutorial/using-docker-compose/index.md @@ -0,0 +1,359 @@ + +[Docker Compose](https://docs.docker.com/compose/) is a tool that was developed to help define and +share multi-container applications. With Compose, we can create a YAML file to define the services +and with a single command, can spin everything up or tear it all down. + +The _big_ advantage of using Compose is you can define your application stack in a file, keep it at the root of +your project repo (it's now version controlled), and easily enable someone else to contribute to your project. +Someone would only need to clone your repo and start the compose app. In fact, you might see quite a few projects +on GitHub/GitLab doing exactly this now. + +So, how do we get started? + +## Installing Docker Compose + +If you installed Docker Desktop/Toolbox for either Windows or Mac, you already have Docker Compose! +Play-with-Docker instances already have Docker Compose installed as well. If you are on +a Linux machine, you will need to install Docker Compose using +[the instructions here](https://docs.docker.com/compose/install/). + +After installation, you should be able to run the following and see version information. + +```bash +docker-compose version +``` + + +## Creating our Compose File + +1. At the root of the app project, create a file named `docker-compose.yml`. + +1. In the compose file, we'll start off by defining the schema version. In most cases, it's best to use + the latest supported version. You can look at the [Compose file reference](https://docs.docker.com/compose/compose-file/) + for the current schema versions and the compatibility matrix. + + ```yaml + version: "3.8" + ``` + +1. Next, we'll define the list of services (or containers) we want to run as part of our application. + + ```yaml hl_lines="3" + version: "3.8" + + services: + ``` + +And now, we'll start migrating a service at a time into the compose file. + + +## Defining the App Service + +To remember, this was the command we were using to define our app container. + +```bash +docker run -dp 3000:3000 \ + -w /app -v "$(pwd):/app" \ + --network todo-app \ + -e MYSQL_HOST=mysql \ + -e MYSQL_USER=root \ + -e MYSQL_PASSWORD=secret \ + -e MYSQL_DB=todos \ + node:12-alpine \ + sh -c "yarn install && yarn run dev" +``` + +If you are using PowerShell then use this command. + +```powershell +docker run -dp 3000:3000 ` + -w /app -v "$(pwd):/app" ` + --network todo-app ` + -e MYSQL_HOST=mysql ` + -e MYSQL_USER=root ` + -e MYSQL_PASSWORD=secret ` + -e MYSQL_DB=todos ` + node:12-alpine ` + sh -c "yarn install && yarn run dev" +``` + +1. First, let's define the service entry and the image for the container. We can pick any name for the service. + The name will automatically become a network alias, which will be useful when defining our MySQL service. + + ```yaml hl_lines="4 5" + version: "3.8" + + services: + app: + image: node:12-alpine + ``` + +1. Typically, you will see the command close to the `image` definition, although there is no requirement on ordering. + So, let's go ahead and move that into our file. + + ```yaml hl_lines="6" + version: "3.8" + + services: + app: + image: node:12-alpine + command: sh -c "yarn install && yarn run dev" + ``` + + +1. Let's migrate the `-p 3000:3000` part of the command by defining the `ports` for the service. We will use the + [short syntax](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-1) here, but there is also a more verbose + [long syntax](https://docs.docker.com/compose/compose-file/compose-file-v3/#long-syntax-1) available as well. + + ```yaml hl_lines="7 8" + version: "3.8" + + services: + app: + image: node:12-alpine + command: sh -c "yarn install && yarn run dev" + ports: + - 3000:3000 + ``` + +1. Next, we'll migrate both the working directory (`-w /app`) and the volume mapping (`-v "$(pwd):/app"`) by using + the `working_dir` and `volumes` definitions. Volumes also has a [short](https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-3) and [long](https://docs.docker.com/compose/compose-file/compose-file-v3/#long-syntax-3) syntax. + + One advantage of Docker Compose volume definitions is we can use relative paths from the current directory. + + ```yaml hl_lines="9 10 11" + version: "3.8" + + services: + app: + image: node:12-alpine + command: sh -c "yarn install && yarn run dev" + ports: + - 3000:3000 + working_dir: /app + volumes: + - ./:/app + ``` + +1. Finally, we need to migrate the environment variable definitions using the `environment` key. + + ```yaml hl_lines="12 13 14 15 16" + version: "3.8" + + services: + app: + image: node:12-alpine + command: sh -c "yarn install && yarn run dev" + ports: + - 3000:3000 + working_dir: /app + volumes: + - ./:/app + environment: + MYSQL_HOST: mysql + MYSQL_USER: root + MYSQL_PASSWORD: secret + MYSQL_DB: todos + ``` + + +### Defining the MySQL Service + +Now, it's time to define the MySQL service. The command that we used for that container was the following: + +```bash +docker run -d \ + --network todo-app --network-alias mysql \ + -v todo-mysql-data:/var/lib/mysql \ + -e MYSQL_ROOT_PASSWORD=secret \ + -e MYSQL_DATABASE=todos \ + mysql:5.7 +``` + +If you are using PowerShell then use this command. + +```powershell +docker run -d ` + --network todo-app --network-alias mysql ` + -v todo-mysql-data:/var/lib/mysql ` + -e MYSQL_ROOT_PASSWORD=secret ` + -e MYSQL_DATABASE=todos ` + mysql:5.7 +``` + +1. We will first define the new service and name it `mysql` so it automatically gets the network alias. We'll + go ahead and specify the image to use as well. + + ```yaml hl_lines="6 7" + version: "3.8" + + services: + app: + # The app service definition + mysql: + image: mysql:5.7 + ``` + +1. Next, we'll define the volume mapping. When we ran the container with `docker run`, the named volume was created + automatically. However, that doesn't happen when running with Compose. We need to define the volume in the top-level + `volumes:` section and then specify the mountpoint in the service config. By simply providing only the volume name, + the default options are used. There are [many more options available](https://docs.docker.com/compose/compose-file/compose-file-v3/#volume-configuration-reference) though. + + ```yaml hl_lines="8 9 10 11 12" + version: "3.8" + + services: + app: + # The app service definition + mysql: + image: mysql:5.7 + volumes: + - todo-mysql-data:/var/lib/mysql + + volumes: + todo-mysql-data: + ``` + +1. Finally, we only need to specify the environment variables. + + ```yaml hl_lines="10 11 12" + version: "3.8" + + services: + app: + # The app service definition + mysql: + image: mysql:5.7 + volumes: + - todo-mysql-data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: secret + MYSQL_DATABASE: todos + + volumes: + todo-mysql-data: + ``` + +At this point, our complete `docker-compose.yml` should look like this: + + +```yaml +version: "3.8" + +services: + app: + image: node:12-alpine + command: sh -c "yarn install && yarn run dev" + ports: + - 3000:3000 + working_dir: /app + volumes: + - ./:/app + environment: + MYSQL_HOST: mysql + MYSQL_USER: root + MYSQL_PASSWORD: secret + MYSQL_DB: todos + + mysql: + image: mysql:5.7 + volumes: + - todo-mysql-data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: secret + MYSQL_DATABASE: todos + +volumes: + todo-mysql-data: +``` + + +## Running our Application Stack + +Now that we have our `docker-compose.yml` file, we can start it up! + +1. Make sure no other copies of the app/db are running first (`docker ps` and `docker rm -f <ids>`). + +1. Start up the application stack using the `docker-compose up` command. We'll add the `-d` flag to run everything in the + background. + + ```bash + docker-compose up -d + ``` + + When we run this, we should see output like this: + + ```plaintext + Creating network "app_default" with the default driver + Creating volume "app_todo-mysql-data" with default driver + Creating app_app_1 ... done + Creating app_mysql_1 ... done + ``` + + You'll notice that the volume was created as well as a network! By default, Docker Compose automatically creates a + network specifically for the application stack (which is why we didn't define one in the compose file). + +1. Let's look at the logs using the `docker-compose logs -f` command. You'll see the logs from each of the services interleaved + into a single stream. This is incredibly useful when you want to watch for timing-related issues. The `-f` flag "follows" the + log, so will give you live output as it's generated. + + If you don't already, you'll see output that looks like this... + + ```plaintext + mysql_1 | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections. + mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) + app_1 | Connected to mysql db at host mysql + app_1 | Listening on port 3000 + ``` + + The service name is displayed at the beginning of the line (often colored) to help distinguish messages. If you want to + view the logs for a specific service, you can add the service name to the end of the logs command (for example, + `docker-compose logs -f app`). + + !!! info "Pro tip - Waiting for the DB before starting the app" + When the app is starting up, it actually sits and waits for MySQL to be up and ready before trying to connect to it. + Docker doesn't have any built-in support to wait for another container to be fully up, running, and ready + before starting another container. For Node-based projects, you can use the + [wait-port](https://github.com/dwmkerr/wait-port) dependency. Similar projects exist for other languages/frameworks. + +1. At this point, you should be able to open your app and see it running. And hey! We're down to a single command! + +## Seeing our App Stack in Docker Dashboard + +If we look at the Docker Dashboard, we'll see that there is a group named **app**. This is the "project name" from Docker +Compose and used to group the containers together. By default, the project name is simply the name of the directory that the +`docker-compose.yml` was located in. + + + +If you twirl down the app, you will see the two containers we defined in the compose file. The names are also a little +more descriptive, as they follow the pattern of `<project-name>_<service-name>_<replica-number>`. So, it's very easy to +quickly see what container is our app and which container is the mysql database. + + + + +## Tearing it All Down + +When you're ready to tear it all down, simply run `docker-compose down` or hit the trash can on the Docker Dashboard +for the entire app. The containers will stop and the network will be removed. + +!!! warning "Removing Volumes" + By default, named volumes in your compose file are NOT removed when running `docker-compose down`. If you want to + remove the volumes, you will need to add the `--volumes` flag. + + The Docker Dashboard does _not_ remove volumes when you delete the app stack. + +Once torn down, you can switch to another project, run `docker-compose up` and be ready to contribute to that project! It really +doesn't get much simpler than that! + + +## Recap + +In this section, we learned about Docker Compose and how it helps us dramatically simplify the defining and +sharing of multi-service applications. We created a Compose file by translating the commands we were +using into the appropriate compose format. + +At this point, we're starting to wrap up the tutorial. However, there are a few best practices about +image building we want to cover, as there is a big issue with the Dockerfile we've been using. So, +let's take a look! diff --git a/docs/tutorial/what-next/index.md b/docs/tutorial/what-next/index.md new file mode 100644 index 0000000..8eca969 --- /dev/null +++ b/docs/tutorial/what-next/index.md @@ -0,0 +1,26 @@ + +Although we're done with our workshop, there's still a LOT more to learn about containers! +We're not going to go deep-dive here, but here are a few other areas to look at next! + +## Container Orchestration + +Running containers in production is tough. You don't want to log into a machine and simply run a +`docker run` or `docker-compose up`. Why not? Well, what happens if the containers die? How do you +scale across several machines? Container orchestration solves this problem. Tools like Kubernetes, +Swarm, Nomad, and ECS all help solve this problem, all in slightly different ways. + +The general idea is that you have "managers" who receive **expected state**. This state might be +"I want to run two instances of my web app and expose port 80." The managers then look at all of the +machines in the cluster and delegate work to "worker" nodes. The managers watch for changes (such as +a container quitting) and then work to make **actual state** reflect the expected state. + + +## Cloud Native Computing Foundation Projects + +The CNCF is a vendor-neutral home for various open-source projects, including Kubernetes, Prometheus, +Envoy, Linkerd, NATS, and more! You can view the [graduated and incubated projects here](https://www.cncf.io/projects/) +and the entire [CNCF Landscape here](https://landscape.cncf.io/). There are a LOT of projects to help +solve problems around monitoring, logging, security, image registries, messaging, and more! + +So, if you're new to the container landscape and cloud-native application development, welcome! Please +connect to the community, ask questions, and keep learning! We're excited to have you! |