Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2ef123150d |
@ -1,2 +1 @@
|
||||
node_modules
|
||||
app/Dockerfile
|
15
.drone.yml
@ -1,15 +0,0 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: git.ericxliu.me
|
||||
username: master
|
||||
password: 1f8af4f82c0bd4dc24c1a03eaea6da13f54fed1c
|
||||
repo: git.ericxliu.me/master/getting-started
|
||||
tags: latest
|
||||
platform: linux/amd64
|
||||
custom_dns: [ 8.8.8.8 ]
|
||||
mtu: 1450
|
30
.github/workflows/build.yml
vendored
@ -1,30 +0,0 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||
DOCKER_HOST: tcp://docker.local:2375
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to the Docker registry
|
||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login ${{ secrets.DOCKER_REGISTRY }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
|
||||
- name: Build the Docker image
|
||||
run: docker build -t ${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest .
|
||||
|
||||
- name: Push the Docker image
|
||||
run: docker push ${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
node_modules
|
||||
app/Dockerfile
|
50
Dockerfile
@ -1,62 +1,36 @@
|
||||
# Install the base requirements for the app.
|
||||
# This stage is to support development.
|
||||
ARG BUILDPLATFORM="linux/amd64"
|
||||
ARG TARGETPLATFORM="linux/amd64"
|
||||
|
||||
FROM --platform=$BUILDPLATFORM python:alpine AS base
|
||||
FROM python:alpine AS base
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN ping -c5 8.8.8.8
|
||||
RUN ping -c5 208.67.222.222
|
||||
RUN ifconfig
|
||||
RUN uname -a
|
||||
RUN cat /etc/resolv.conf
|
||||
RUN route
|
||||
RUN ip address
|
||||
RUN ip route
|
||||
RUN nslookup google.com
|
||||
# RUN apk update
|
||||
# RUN apk add --no-cache curl dig
|
||||
# RUN dig https://google.com
|
||||
# RUN curl -IL google.com
|
||||
# RUN curl ifconfig.me
|
||||
|
||||
|
||||
|
||||
|
||||
RUN pip3 install --default-timeout=100 -r requirements.txt
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:18-alpine AS app-base
|
||||
WORKDIR /app
|
||||
COPY app/package.json app/yarn.lock ./
|
||||
COPY app/spec ./spec
|
||||
COPY app/src ./src
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Run tests to validate app
|
||||
FROM app-base AS test
|
||||
FROM node:12-alpine AS app-base
|
||||
WORKDIR /app
|
||||
COPY app/package.json app/yarn.lock ./
|
||||
RUN yarn install
|
||||
COPY app/spec ./spec
|
||||
COPY app/src ./src
|
||||
RUN yarn test
|
||||
|
||||
# Clear out the node_modules and create the zip
|
||||
FROM app-base AS app-zip-creator
|
||||
COPY --from=test /app/package.json /app/yarn.lock ./
|
||||
COPY app/spec ./spec
|
||||
COPY app/src ./src
|
||||
RUN apk add zip && \
|
||||
RUN rm -rf node_modules && \
|
||||
apk add zip && \
|
||||
zip -r /app.zip /app
|
||||
|
||||
# Dev-ready container - actual files will be mounted in
|
||||
FROM --platform=$BUILDPLATFORM base AS dev
|
||||
FROM base AS dev
|
||||
CMD ["mkdocs", "serve", "-a", "0.0.0.0:8000"]
|
||||
|
||||
# Do the actual build of the mkdocs site
|
||||
FROM --platform=$BUILDPLATFORM base AS build
|
||||
FROM base AS build
|
||||
COPY . .
|
||||
RUN mkdocs build
|
||||
|
||||
# Extract the static content from the build
|
||||
# and use a nginx image to serve the content
|
||||
FROM --platform=$TARGETPLATFORM nginx:alpine
|
||||
FROM nginx:alpine
|
||||
COPY --from=app-zip-creator /app.zip /usr/share/nginx/html/assets/app.zip
|
||||
COPY --from=build /app/site /usr/share/nginx/html
|
||||
|
22
Jenkinsfile
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
pipeline {
|
||||
options {
|
||||
timeout(time: 1, unit: 'HOURS')
|
||||
}
|
||||
agent {
|
||||
label 'ubuntu-1804 && amd64 && docker'
|
||||
}
|
||||
stages {
|
||||
stage('build and push') {
|
||||
when {
|
||||
branch 'master'
|
||||
}
|
||||
sh "docker build docker/getting-started ."
|
||||
|
||||
steps {
|
||||
withDockerRegistry([url: "", credentialsId: "dockerbuildbot-index.docker.io"]) {
|
||||
sh("docker push docker/getting-started")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
README.md
@ -1,13 +1,12 @@
|
||||
# Docker Getting Started Tutorial
|
||||
|
||||
This tutorial was written with the intent of helping folks get up and running
|
||||
This tutorial has been written with the intent of helping folks get up and running
|
||||
with containers and is designed to work with Docker Desktop. While not going too much
|
||||
into depth, it covers the following topics:
|
||||
|
||||
- Running your first container
|
||||
- Building containers
|
||||
- Learning what containers are
|
||||
- Running and removing containers
|
||||
- Learning what containers are running and removing them
|
||||
- Using volumes to persist data
|
||||
- Using bind mounts to support development
|
||||
- Using container networking to support multi-container applications
|
||||
@ -31,14 +30,13 @@ This project has a `docker-compose.yml` file, which will start the mkdocs applic
|
||||
local machine and help you see changes instantly.
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
If you find typos or other issues with the tutorial, feel free to create a PR and suggest fixes!
|
||||
|
||||
If you have ideas on how to make the tutorial better or want to suggest adding new content, please open an
|
||||
issue first before working on your idea. While we love input, we want to keep the tutorial scoped to new-comers.
|
||||
If you have ideas on how to make the tutorial better or new content, please open an issue first before working on your idea. While we love input, we want to keep the tutorial is scoped to new-comers.
|
||||
As such, we may reject ideas for more advanced requests and don't want you to lose any work you might
|
||||
have done. So, ask first and we'll gladly hear your thoughts!
|
||||
|
@ -9,14 +9,12 @@
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3",
|
||||
"sqlite3": "^5.1.2",
|
||||
"uuid": "^9.0.0",
|
||||
"wait-port": "^1.0.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"ansi-regex": "5.0.1"
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1",
|
||||
"mysql": "^2.17.1",
|
||||
"sqlite3": "^4.1.0",
|
||||
"uuid": "^3.3.3",
|
||||
"wait-port": "^0.2.2"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "all",
|
||||
@ -26,8 +24,8 @@
|
||||
"singleQuote": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.3.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"prettier": "^2.7.1"
|
||||
"jest": "^24.9.0",
|
||||
"nodemon": "^1.19.2",
|
||||
"prettier": "^1.18.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
const db = require('../../src/persistence/sqlite');
|
||||
const fs = require('fs');
|
||||
const location = process.env.SQLITE_DB_LOCATION || '/etc/todos/todo.db';
|
||||
|
||||
const ITEM = {
|
||||
id: '7aef3d7c-d301-4846-8358-2a91ec9d6be3',
|
||||
@ -9,8 +8,8 @@ const ITEM = {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
if (fs.existsSync(location)) {
|
||||
fs.unlinkSync(location);
|
||||
if (fs.existsSync('/etc/todos/todo.db')) {
|
||||
fs.unlinkSync('/etc/todos/todo.db');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
const db = require('../../src/persistence');
|
||||
const addItem = require('../../src/routes/addItem');
|
||||
const ITEM = { id: 12345 };
|
||||
const {v4 : uuid} = require('uuid');
|
||||
const uuid = require('uuid/v4');
|
||||
|
||||
jest.mock('uuid', () => ({ v4: jest.fn() }));
|
||||
jest.mock('uuid/v4', () => jest.fn());
|
||||
|
||||
jest.mock('../../src/persistence', () => ({
|
||||
removeItem: jest.fn(),
|
||||
|
@ -6,7 +6,7 @@ const addItem = require('./routes/addItem');
|
||||
const updateItem = require('./routes/updateItem');
|
||||
const deleteItem = require('./routes/deleteItem');
|
||||
|
||||
app.use(express.json());
|
||||
app.use(require('body-parser').json());
|
||||
app.use(express.static(__dirname + '/static'));
|
||||
|
||||
app.get('/items', getItems);
|
||||
|
@ -1,6 +1,6 @@
|
||||
const waitPort = require('wait-port');
|
||||
const fs = require('fs');
|
||||
const mysql = require('mysql2');
|
||||
const mysql = require('mysql');
|
||||
|
||||
const {
|
||||
MYSQL_HOST: HOST,
|
||||
@ -21,12 +21,7 @@ async function init() {
|
||||
const password = PASSWORD_FILE ? fs.readFileSync(PASSWORD_FILE) : PASSWORD;
|
||||
const database = DB_FILE ? fs.readFileSync(DB_FILE) : DB;
|
||||
|
||||
await waitPort({
|
||||
host,
|
||||
port: 3306,
|
||||
timeout: 10000,
|
||||
waitForDns: true,
|
||||
});
|
||||
await waitPort({ host, port : 3306});
|
||||
|
||||
pool = mysql.createPool({
|
||||
connectionLimit: 5,
|
||||
@ -34,12 +29,11 @@ async function init() {
|
||||
user,
|
||||
password,
|
||||
database,
|
||||
charset: 'utf8mb4',
|
||||
});
|
||||
|
||||
return new Promise((acc, rej) => {
|
||||
pool.query(
|
||||
'CREATE TABLE IF NOT EXISTS todo_items (id varchar(36), name varchar(255), completed boolean) DEFAULT CHARSET utf8mb4',
|
||||
'CREATE TABLE IF NOT EXISTS todo_items (id varchar(36), name varchar(255), completed boolean)',
|
||||
err => {
|
||||
if (err) return rej(err);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const db = require('../persistence');
|
||||
const {v4 : uuid} = require('uuid');
|
||||
const uuid = require('uuid/v4');
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const item = {
|
||||
|
5902
app/yarn.lock
2
build.sh
@ -9,6 +9,6 @@ else
|
||||
fi
|
||||
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \
|
||||
-t docker/getting-started:latest \
|
||||
$( (( $WILL_PUSH == 1 )) && printf %s '--push' ) .
|
||||
|
Before Width: | Height: | Size: 125 KiB |
@ -1,50 +1,7 @@
|
||||
## 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.
|
||||
|
||||
![Hub vulnerability scanning](hvs.png){: style=width:75% }
|
||||
{: .text-center }
|
||||
|
||||
## Image Layering
|
||||
|
||||
Did you know that you can look at how an image is composed? Using the `docker image history`
|
||||
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
|
||||
@ -58,23 +15,23 @@ command, you can see the command that was used to create each layer within an im
|
||||
|
||||
```plaintext
|
||||
IMAGE CREATED CREATED BY SIZE COMMENT
|
||||
05bd8640b718 53 minutes ago CMD ["node" "src/index.js"] 0B buildkit.dockerfile.v0
|
||||
<missing> 53 minutes ago RUN /bin/sh -c yarn install --production # b… 83.3MB buildkit.dockerfile.v0
|
||||
<missing> 53 minutes ago COPY . . # buildkit 4.59MB buildkit.dockerfile.v0
|
||||
<missing> 55 minutes ago WORKDIR /app 0B buildkit.dockerfile.v0
|
||||
<missing> 10 days ago /bin/sh -c #(nop) CMD ["node"] 0B
|
||||
<missing> 10 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
|
||||
<missing> 10 days ago /bin/sh -c #(nop) COPY file:4d192565a7220e13… 388B
|
||||
<missing> 10 days ago /bin/sh -c apk add --no-cache --virtual .bui… 7.85MB
|
||||
<missing> 10 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.22.19 0B
|
||||
<missing> 10 days ago /bin/sh -c addgroup -g 1000 node && addu… 152MB
|
||||
<missing> 10 days ago /bin/sh -c #(nop) ENV NODE_VERSION=18.12.1 0B
|
||||
<missing> 11 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
|
||||
<missing> 11 days ago /bin/sh -c #(nop) ADD file:57d621536158358b1… 5.29MB
|
||||
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 line 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 to
|
||||
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
|
||||
@ -95,7 +52,7 @@ times for your container images.
|
||||
Let's look at the Dockerfile we were using one more time...
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN yarn install --production
|
||||
@ -107,14 +64,14 @@ You might remember that when we made a change to the image, the yarn dependencie
|
||||
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 start by copying only that file in first,
|
||||
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:18-alpine
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --production
|
||||
@ -131,9 +88,9 @@ a change to the `package.json`. Make sense?
|
||||
`.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
|
||||
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 as well as further best practices,
|
||||
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/).
|
||||
|
||||
@ -146,23 +103,34 @@ a change to the `package.json`. Make sense?
|
||||
You should see output like this...
|
||||
|
||||
```plaintext
|
||||
[+] Building 16.1s (10/10) FINISHED
|
||||
=> [internal] load build definition from Dockerfile 0.0s
|
||||
=> => transferring dockerfile: 175B 0.0s
|
||||
=> [internal] load .dockerignore 0.0s
|
||||
=> => transferring context: 2B 0.0s
|
||||
=> [internal] load metadata for docker.io/library/node:18-alpine 0.0s
|
||||
=> [internal] load build context 0.8s
|
||||
=> => transferring context: 53.37MB 0.8s
|
||||
=> [1/5] FROM docker.io/library/node:18-alpine 0.0s
|
||||
=> CACHED [2/5] WORKDIR /app 0.0s
|
||||
=> [3/5] COPY package.json yarn.lock ./ 0.2s
|
||||
=> [4/5] RUN yarn install --production 14.0s
|
||||
=> [5/5] COPY . . 0.5s
|
||||
=> exporting to image 0.6s
|
||||
=> => exporting layers 0.6s
|
||||
=> => writing image sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25 0.0s
|
||||
=> => naming to docker.io/library/getting-started 0.0s
|
||||
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.
|
||||
@ -171,35 +139,38 @@ a change to the `package.json`. Make sense?
|
||||
|
||||
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="10 11 12"
|
||||
[+] Building 1.2s (10/10) FINISHED
|
||||
=> [internal] load build definition from Dockerfile 0.0s
|
||||
=> => transferring dockerfile: 37B 0.0s
|
||||
=> [internal] load .dockerignore 0.0s
|
||||
=> => transferring context: 2B 0.0s
|
||||
=> [internal] load metadata for docker.io/library/node:18-alpine 0.0s
|
||||
=> [internal] load build context 0.2s
|
||||
=> => transferring context: 450.43kB 0.2s
|
||||
=> [1/5] FROM docker.io/library/node:18-alpine 0.0s
|
||||
=> CACHED [2/5] WORKDIR /app 0.0s
|
||||
=> CACHED [3/5] COPY package.json yarn.lock ./ 0.0s
|
||||
=> CACHED [4/5] RUN yarn install --production 0.0s
|
||||
=> [5/5] COPY . . 0.5s
|
||||
=> exporting to image 0.3s
|
||||
=> => exporting layers 0.3s
|
||||
=> => writing image sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda 0.0s
|
||||
=> => naming to docker.io/library/getting-started 0.0s
|
||||
```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! You'll see that several steps are using
|
||||
previously cached layers. So, hooray! We're using the build cache. Pushing and pulling this image and updates to it
|
||||
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 which help us by using multiple stages to create an image. They offer several advantages including:
|
||||
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
|
||||
@ -207,7 +178,7 @@ tool which help us by using multiple stages to create an image. They offer sever
|
||||
### 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. You might also be using tools such as Maven or Gradle to help build the app.
|
||||
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
|
||||
@ -220,7 +191,7 @@ 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 with Maven. In the second
|
||||
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).
|
||||
|
||||
@ -228,11 +199,11 @@ 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. Although if we aren't performing server-side rendering, we don't even need a Node environment
|
||||
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:18 AS build
|
||||
FROM node:12 AS build
|
||||
WORKDIR /app
|
||||
COPY package* yarn.lock ./
|
||||
RUN yarn install
|
||||
@ -244,13 +215,13 @@ FROM nginx:alpine
|
||||
COPY --from=build /app/build /usr/share/nginx/html
|
||||
```
|
||||
|
||||
Here, we are using a `node:18` image to perform the build (maximizing layer caching) and then copying the output
|
||||
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.
|
||||
|
||||
|
@ -25,29 +25,29 @@ You'll notice a few flags being used. Here's some more info on them:
|
||||
docker run -dp 80:80 docker/getting-started
|
||||
```
|
||||
|
||||
## The Docker Dashboard
|
||||
## The VSCode Extension
|
||||
|
||||
Before going any further, we want to highlight the Docker Dashboard, which gives
|
||||
you a quick view of the containers running on your machine. It provides you
|
||||
access to container logs, lets you get a shell inside the container, and allows you to easily manage container lifecycle (stop, remove, etc.).
|
||||
Before going too far, we want to highlight the Docker VSCode Extension, 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
|
||||
To access the extension, follow the instructions
|
||||
[here](https://code.visualstudio.com/docs/containers/overview). If you open the extension
|
||||
now, you will see this tutorial running! The container name (`angry_taussig` below) is a
|
||||
randomly created name. So, you'll most likely have a different name.
|
||||
|
||||
![Tutorial container running in Docker Dashboard](tutorial-in-dashboard.png)
|
||||
![Tutorial container running in Docker Extension](vs-tutorial-in-extension.png)
|
||||
|
||||
|
||||
## What is a container?
|
||||
|
||||
Now that you've successfully run a container, let's ask ourselves what _is_ a container? Simply put, a container is
|
||||
another process on your machine that has been isolated from all other processes
|
||||
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"
|
||||
!!! 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,
|
||||
@ -58,8 +58,8 @@ in Linux for a long time. Docker has worked to make these capabilities approacha
|
||||
## 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 include everything
|
||||
needed to run the application - all dependencies, configuration, scripts, binaries, etc. The
|
||||
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.
|
||||
|
||||
@ -67,6 +67,6 @@ We'll dive deeper into images later on, covering topics such as layering, best p
|
||||
|
||||
!!! 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 whereas a container adds additional isolation that is not
|
||||
filesystem is simply coming from the image. But, a container adds additional isolation not
|
||||
available when simply using chroot.
|
||||
|
||||
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 121 KiB |
@ -4,12 +4,12 @@ application stack. The following question often arises - "Where will MySQL run?
|
||||
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.
|
||||
- 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.
|
||||
which adds complexity to container startup/shutdown
|
||||
|
||||
And there are more reasons. So, we will update our application to work like this:
|
||||
|
||||
@ -37,8 +37,8 @@ For now, we will create the network first and attach the MySQL container at star
|
||||
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/)).
|
||||
1. Start a MySQL container and attach it 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/)) (replace the ` \ ` characters with `` ` `` in Windows PowerShell).
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
@ -46,18 +46,7 @@ For now, we will create the network first and attach the MySQL container at star
|
||||
-v todo-mysql-data:/var/lib/mysql \
|
||||
-e MYSQL_ROOT_PASSWORD=secret \
|
||||
-e MYSQL_DATABASE=todos \
|
||||
mysql:8.0
|
||||
```
|
||||
|
||||
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:8.0
|
||||
mysql:5.7
|
||||
```
|
||||
|
||||
You'll also see we specified the `--network-alias` flag. We'll come back to that in just a moment.
|
||||
@ -97,8 +86,6 @@ For now, we will create the network first and attach the MySQL container at star
|
||||
|
||||
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
|
||||
|
||||
@ -125,7 +112,7 @@ which ships with a _lot_ of tools that are useful for troubleshooting or debuggi
|
||||
And you'll get an output like this...
|
||||
|
||||
```text
|
||||
; <<>> DiG 9.18.8 <<>> mysql
|
||||
; <<>> DiG 9.14.1 <<>> mysql
|
||||
;; global options: +cmd
|
||||
;; Got answer:
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
|
||||
@ -151,8 +138,6 @@ which ships with a _lot_ of tools that are useful for troubleshooting or debuggi
|
||||
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!
|
||||
|
||||
When you're done, run `exit` to close out of the container.
|
||||
|
||||
|
||||
## Running our App with MySQL
|
||||
|
||||
@ -165,13 +150,13 @@ The todo app supports the setting of a few environment variables to specify MySQ
|
||||
|
||||
!!! 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, a former lead of security at Docker,
|
||||
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.
|
||||
also support env vars with a `_FILE` suffix to point to a file containing the file.
|
||||
|
||||
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
|
||||
@ -180,31 +165,17 @@ The todo app supports the setting of a few environment variables to specify MySQ
|
||||
|
||||
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.
|
||||
1. We'll specify each of the environment variables above, as well as connect the container to our app network (replace the ` \ ` characters with `` ` `` in Windows PowerShell).
|
||||
|
||||
```bash hl_lines="3 4 5 6 7"
|
||||
docker run -dp 3000:3000 \
|
||||
-w /app -v "$(pwd):/app" \
|
||||
-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:18-alpine \
|
||||
sh -c "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:18-alpine `
|
||||
node:12-alpine \
|
||||
sh -c "yarn install && yarn run dev"
|
||||
```
|
||||
|
||||
@ -214,10 +185,9 @@ With all of that explained, let's start our dev-ready container!
|
||||
```plaintext hl_lines="7"
|
||||
# Previous log messages omitted
|
||||
$ nodemon src/index.js
|
||||
[nodemon] 2.0.20
|
||||
[nodemon] 1.19.2
|
||||
[nodemon] to restart at any time, enter `rs`
|
||||
[nodemon] watching path(s): *.*
|
||||
[nodemon] watching extensions: js,mjs,json
|
||||
[nodemon] watching dir(s): *.*
|
||||
[nodemon] starting `node src/index.js`
|
||||
Connected to mysql db at host mysql
|
||||
Listening on port 3000
|
||||
@ -229,7 +199,7 @@ With all of that explained, let's start our dev-ready container!
|
||||
is **secret**.
|
||||
|
||||
```bash
|
||||
docker exec -it <mysql-container-id> mysql -p todos
|
||||
docker exec -ti <mysql-container-id> mysql -p todos
|
||||
```
|
||||
|
||||
And in the mysql shell, run the following:
|
||||
@ -246,10 +216,10 @@ With all of that explained, let's start our dev-ready container!
|
||||
|
||||
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
|
||||
If you take a quick look at the Docker extension, 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!
|
||||
|
||||
![Docker Dashboard showing two ungrouped app containers](dashboard-multi-container-app.png)
|
||||
![Docker Dashboard showing two ungrouped app containers](vs-multi-container-app.png)
|
||||
|
||||
## Recap
|
||||
|
||||
|
BIN
docs/tutorial/multi-container-apps/vs-multi-container-app.png
Normal file
After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 113 KiB |
@ -37,11 +37,11 @@ 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:18-alpine
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN yarn install --production
|
||||
CMD ["node", "./src/index.js"]
|
||||
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.
|
||||
@ -54,7 +54,7 @@ see a few flaws in the Dockerfile below. But, don't worry! We'll go over them.
|
||||
|
||||
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:18-alpine` image. But, since we
|
||||
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
|
||||
@ -97,10 +97,10 @@ command (remember that from earlier?).
|
||||
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
|
||||
If you take a quick look at the VSCode extension, you should see your two containers running now
|
||||
(this tutorial and your freshly launched app container)!
|
||||
|
||||
![Docker Dashboard with tutorial and app containers running](dashboard-two-containers.png)
|
||||
![Docker Dashboard with tutorial and app containers running](vs-two-containers.png)
|
||||
|
||||
|
||||
## Recap
|
||||
|
BIN
docs/tutorial/our-application/vs-two-containers.png
Normal file
After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 166 KiB |
@ -24,7 +24,7 @@ What you'll see is that the files created in one container aren't available in a
|
||||
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, find your Ubuntu container, click on the "triple dot" menu to get additional actions, and click on the "Open in terminal" menu item.
|
||||
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.
|
||||
|
||||
![Dashboard open CLI into ubuntu container](dashboard-open-cli-ubuntu.png){: style=width:75% }
|
||||
{: .text-center }
|
||||
@ -54,10 +54,7 @@ What you'll see is that the files created in one container aren't available in a
|
||||
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>
|
||||
```
|
||||
1. Go ahead and remove the first container using the `docker rm -f` command.
|
||||
|
||||
## Container Volumes
|
||||
|
||||
@ -94,7 +91,7 @@ Every time you use the volume, Docker will make sure the correct data is provide
|
||||
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. Stop the todo app container once again in the Dashboard (or with `docker rm -f <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.
|
||||
@ -108,7 +105,7 @@ Every time you use the volume, Docker will make sure the correct data is provide
|
||||
![Items added to todo list](items-added.png){: 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. Remove the container for the todo app. Use the Dashboard or `docker ps` to get the ID and then `docker rm -f <id>` to remove it.
|
||||
|
||||
1. Start a new container using the same command from above.
|
||||
|
||||
|
@ -36,8 +36,7 @@ an example command that you will need to run to push to this repo.
|
||||
|
||||
To fix this, we need to "tag" our existing image we've built to give it another name.
|
||||
|
||||
1. Login to Docker Hub by either clicking on the "Sign In" button in Docker Desktop or using the
|
||||
command `docker login -u YOUR-USER-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.
|
||||
@ -59,7 +58,7 @@ an example command that you will need to run to push to this repo.
|
||||
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. Open your browser to [Play with Docker](http://play-with-docker.com).
|
||||
|
||||
1. Log in with your Docker Hub account.
|
||||
|
||||
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 117 KiB |
@ -74,17 +74,16 @@ ways that we can remove the old container. Feel free to choose the path that you
|
||||
|
||||
### Removing a container using the Docker Dashboard
|
||||
|
||||
If you open the Docker dashboard, you can remove a container with two clicks! It's certainly
|
||||
If you open the VSCode Extension, 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. With the extension opened, navigate to the container and right click.
|
||||
|
||||
1. Click on the trash can icon to delete the container.
|
||||
1. Click on the remove container option
|
||||
|
||||
1. Confirm the removal and you're done!
|
||||
|
||||
![Docker Dashboard - removing a container](dashboard-removing-container.png)
|
||||
![Docker Dashboard - removing a container](vs-removing-container.png)
|
||||
|
||||
|
||||
### Starting our updated app container
|
||||
|
BIN
docs/tutorial/updating-our-app/vs-removing-container.png
Normal file
After Width: | Height: | Size: 153 KiB |
@ -14,7 +14,7 @@ changes and then restart the application. There are equivalent tools in most oth
|
||||
## 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).
|
||||
volume drivers are available to support other uses 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 |
|
||||
| - | ------------- | ----------- |
|
||||
@ -34,36 +34,21 @@ To run our container to support a development workflow, we will do the following
|
||||
|
||||
So, let's do it!
|
||||
|
||||
1. Make sure you don't have any of your own `getting-started` containers running (only the tutorial itself should be running).
|
||||
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:
|
||||
1. Run the following command (replace the ` \ ` characters with `` ` `` in Windows PowerShell). We'll explain what's going on afterwards:
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 \
|
||||
-w /app -v "$(pwd):/app" \
|
||||
node:18-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:18-alpine `
|
||||
-w /app -v ${PWD}:/app \
|
||||
node:12-alpine \
|
||||
sh -c "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:18-alpine` - the image to use. Note that this is the base image for our app from the Dockerfile
|
||||
- `-w /app` - sets the "working directory" or the current directory that the command will run from
|
||||
- `-v ${PWD}:/app` - bind mount the current directory from the host in the container into the `/app` directory
|
||||
- `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`.
|
||||
@ -73,10 +58,9 @@ So, let's do it!
|
||||
```bash
|
||||
docker logs -f <container-id>
|
||||
$ nodemon src/index.js
|
||||
[nodemon] 2.0.20
|
||||
[nodemon] 1.19.2
|
||||
[nodemon] to restart at any time, enter `rs`
|
||||
[nodemon] watching path(s): *.*
|
||||
[nodemon] watching extensions: js,mjs,json
|
||||
[nodemon] watching dir(s): *.*
|
||||
[nodemon] starting `node src/index.js`
|
||||
Using sqlite database at /etc/todos/todo.db
|
||||
Listening on port 3000
|
||||
@ -85,7 +69,7 @@ So, let's do it!
|
||||
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.
|
||||
"Add". This change will be on line 109.
|
||||
|
||||
```diff
|
||||
- {submitting ? 'Adding...' : 'Add Item'}
|
||||
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 126 KiB |
@ -12,18 +12,35 @@ So, how do we get started?
|
||||
|
||||
## Installing Docker Compose
|
||||
|
||||
If you installed Docker Desktop for Windows, Mac, or Linux you already have 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
|
||||
another system, you can install Docker Compose using [the instructions here](https://docs.docker.com/compose/install/).
|
||||
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. Inside of the app folder, create a file named `docker-compose.yml` (next to the `Dockerfile` and `package.json` files).
|
||||
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 a list of services (or containers) we want to run as part of our application.
|
||||
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.7"
|
||||
```
|
||||
|
||||
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.7"
|
||||
|
||||
services:
|
||||
```
|
||||
|
||||
@ -32,62 +49,70 @@ 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.
|
||||
To remember, this was the command we were using to define our app container (replace the ` \ ` characters with `` ` `` in Windows PowerShell).
|
||||
|
||||
```bash
|
||||
docker run -dp 3000:3000 \
|
||||
-w /app -v "$(pwd):/app" \
|
||||
-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:18-alpine \
|
||||
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="2 3"
|
||||
```yaml hl_lines="4 5"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:18-alpine
|
||||
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="4"
|
||||
```yaml hl_lines="6"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:18-alpine
|
||||
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/#short-syntax-2) here, but there is also a more verbose
|
||||
[long syntax](https://docs.docker.com/compose/compose-file/#long-syntax-2) available as well.
|
||||
[short syntax](https://docs.docker.com/compose/compose-file/#short-syntax-1) here, but there is also a more verbose
|
||||
[long syntax](https://docs.docker.com/compose/compose-file/#long-syntax-1) available as well.
|
||||
|
||||
```yaml hl_lines="7 8"
|
||||
version: "3.7"
|
||||
|
||||
```yaml hl_lines="5 6"
|
||||
services:
|
||||
app:
|
||||
image: node:18-alpine
|
||||
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/#short-syntax-4) and [long](https://docs.docker.com/compose/compose-file/#long-syntax-4) syntax.
|
||||
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/#short-syntax-3) and [long](https://docs.docker.com/compose/compose-file/#long-syntax-3) syntax.
|
||||
|
||||
One advantage of Docker Compose volume definitions is we can use relative paths from the current directory.
|
||||
|
||||
```yaml hl_lines="7 8 9"
|
||||
```yaml hl_lines="9 10 11"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:18-alpine
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
ports:
|
||||
- 3000:3000
|
||||
@ -98,10 +123,12 @@ docker run -dp 3000:3000 \
|
||||
|
||||
1. Finally, we need to migrate the environment variable definitions using the `environment` key.
|
||||
|
||||
```yaml hl_lines="10 11 12 13 14"
|
||||
```yaml hl_lines="12 13 14 15 16"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:18-alpine
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
ports:
|
||||
- 3000:3000
|
||||
@ -118,7 +145,7 @@ docker run -dp 3000:3000 \
|
||||
|
||||
### Defining the MySQL Service
|
||||
|
||||
Now, it's time to define the MySQL service. The command that we used for that container was the following:
|
||||
Now, it's time to define the MySQL service. The command that we used for that container was the following (replace the ` \ ` characters with `` ` `` in Windows PowerShell):
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
@ -126,31 +153,35 @@ docker run -d \
|
||||
-v todo-mysql-data:/var/lib/mysql \
|
||||
-e MYSQL_ROOT_PASSWORD=secret \
|
||||
-e MYSQL_DATABASE=todos \
|
||||
mysql:8.0
|
||||
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="4 5"
|
||||
```yaml hl_lines="6 7"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
# The app service definition
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
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/#volumes-top-level-element) though.
|
||||
the default options are used. There are [many more options available](https://docs.docker.com/compose/compose-file/#volume-configuration-reference) though.
|
||||
|
||||
```yaml hl_lines="8 9 10 11 12"
|
||||
version: "3.7"
|
||||
|
||||
```yaml hl_lines="6 7 8 9 10"
|
||||
services:
|
||||
app:
|
||||
# The app service definition
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- todo-mysql-data:/var/lib/mysql
|
||||
|
||||
@ -160,12 +191,14 @@ docker run -d \
|
||||
|
||||
1. Finally, we only need to specify the environment variables.
|
||||
|
||||
```yaml hl_lines="8 9 10"
|
||||
```yaml hl_lines="10 11 12"
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
# The app service definition
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- todo-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
@ -180,9 +213,11 @@ At this point, our complete `docker-compose.yml` should look like this:
|
||||
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: node:18-alpine
|
||||
image: node:12-alpine
|
||||
command: sh -c "yarn install && yarn run dev"
|
||||
ports:
|
||||
- 3000:3000
|
||||
@ -196,7 +231,7 @@ services:
|
||||
MYSQL_DB: todos
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
image: mysql:5.7
|
||||
volumes:
|
||||
- todo-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
@ -214,40 +249,41 @@ 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
|
||||
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
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
When we run this, we should see output like this:
|
||||
|
||||
```plaintext
|
||||
[+] Running 3/3
|
||||
⠿ Network app_default Created 0.0s
|
||||
⠿ Container app-mysql-1 Started 0.4s
|
||||
⠿ Container app-app-1 Started 0.4s
|
||||
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
|
||||
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 | 2022-11-23T04:01:20.185015Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.31' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
|
||||
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`).
|
||||
`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.
|
||||
@ -257,33 +293,29 @@ Now that we have our `docker-compose.yml` file, we can start it up!
|
||||
|
||||
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
|
||||
## Seeing our App Stack in the Docker Extension
|
||||
|
||||
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 we look at the Docker Extension, we can changing our grouping options using the 'cog' and 'group by'. In this instance we want to see containers sharing a network.
|
||||
|
||||
![Docker Dashboard with app project](dashboard-app-project-collapsed.png)
|
||||
![VS Extension with network](vs-app-project-collapsed.png)
|
||||
|
||||
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.
|
||||
If you twirl down the network, you will see the two containers we defined in the compose file.
|
||||
|
||||
![Docker Dashboard with app project expanded](dashboard-app-project-expanded.png)
|
||||
![VS Extension with app network expanded](vs-app-project-expanded.png)
|
||||
|
||||
|
||||
## 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
|
||||
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
|
||||
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
|
||||
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!
|
||||
|
||||
|
||||
|
BIN
docs/tutorial/using-docker-compose/vs-app-project-collapsed.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
docs/tutorial/using-docker-compose/vs-app-project-expanded.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
docs/tutorial/vs-tutorial-in-extension.png
Normal file
After Width: | Height: | Size: 316 KiB |
@ -5,7 +5,7 @@ We're not going to go deep-dive here, but here are a few other areas to look at
|
||||
## 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
|
||||
`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.
|
||||
|
||||
|
@ -9,7 +9,7 @@ repo_url: https://github.com/docker/getting-started
|
||||
edit_uri: ""
|
||||
|
||||
# Copyright
|
||||
copyright: 'Copyright © 2020-2022 Docker'
|
||||
copyright: 'Copyright © 2020 Docker'
|
||||
|
||||
# Configuration
|
||||
theme:
|
||||
|
@ -1,5 +1,5 @@
|
||||
mkdocs==1.3.0
|
||||
mkdocs==1.0.4
|
||||
mkdocs-material==4.6.3
|
||||
mkdocs-minify-plugin==0.2.3
|
||||
pygments==2.7.4
|
||||
pygments==2.6.1
|
||||
pymdown-extensions==7.0
|
||||
|