├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── package.json ├── spec │ ├── persistence │ │ └── sqlite.spec.js │ └── routes │ │ ├── addItem.spec.js │ │ ├── deleteItem.spec.js │ │ ├── getItems.spec.js │ │ └── updateItem.spec.js ├── src │ ├── index.js │ ├── persistence │ │ ├── index.js │ │ ├── mysql.js │ │ └── sqlite.js │ ├── routes │ │ ├── addItem.js │ │ ├── deleteItem.js │ │ ├── getItems.js │ │ └── updateItem.js │ └── static │ │ ├── css │ │ ├── bootstrap.min.css │ │ ├── font-awesome │ │ │ ├── all.min.css │ │ │ ├── fa-brands-400.eot │ │ │ ├── fa-brands-400.svg#fontawesome │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.eot │ │ │ ├── fa-regular-400.svg#fontawesome │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff │ │ │ ├── fa-regular-400.woff2 │ │ │ ├── fa-solid-900.eot │ │ │ ├── fa-solid-900.svg#fontawesome │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff │ │ │ └── fa-solid-900.woff2 │ │ └── styles.css │ │ ├── index.html │ │ └── js │ │ ├── app.js │ │ ├── babel.min.js │ │ ├── react-bootstrap.js │ │ ├── react-dom.production.min.js │ │ └── react.production.min.js └── yarn.lock ├── build.sh ├── configure.js ├── docker-compose.yml ├── docs_en ├── css │ ├── dark-mode.css │ └── styles.css ├── fonts │ └── hinted-Geomanist-Book.ttf ├── images │ ├── docker-labs-logo.svg │ └── pwd-badge.png ├── index.md ├── js │ ├── custom.js │ ├── jquery.raptorize.1.0.js │ ├── raptor-sound.mp3 │ ├── raptor-sound.ogg │ └── raptor.png ├── pwd-tips │ ├── editor-button.png │ ├── editor-display.png │ └── index.md └── tutorial │ ├── image-building-best-practices │ └── index.md │ ├── index.md │ ├── multi-container-apps │ ├── index.md │ └── multi-app-architecture.png │ ├── our-application │ ├── index.md │ ├── todo-list-empty.png │ └── todo-list-sample.png │ ├── persisting-our-data │ ├── index.md │ └── items-added.png │ ├── sharing-our-app │ ├── index.md │ └── push-command.png │ ├── updating-our-app │ ├── index.md │ └── todo-list-updated-empty-text.png │ ├── using-bind-mounts │ ├── index.md │ └── updated-add-button.png │ ├── using-docker-compose │ └── index.md │ └── what-next │ └── index.md ├── docs_es ├── consejos-para-usar-pwd │ ├── editor-button.png │ ├── editor-display.png │ └── index.md ├── css │ ├── dark-mode.css │ └── styles.css ├── fonts │ └── hinted-Geomanist-Book.ttf ├── images │ ├── docker-labs-logo.svg │ └── pwd-badge.png ├── index.md ├── js │ ├── custom.js │ ├── jquery.raptorize.1.0.js │ ├── raptor-sound.mp3 │ ├── raptor-sound.ogg │ └── raptor.png └── tutorial │ ├── actualizando-nuestra-aplicacion │ ├── index.md │ └── todo-list-updated-empty-text.png │ ├── aplicaciones-multi-contenedor │ ├── index.md │ └── multi-app-architecture.png │ ├── buenas-practicas-para-construccion-de-imagenes │ └── index.md │ ├── compartiendo-nuestra-aplicacion │ ├── index.md │ └── push-command.png │ ├── index.md │ ├── nuestra-aplicacion │ ├── index.md │ ├── todo-list-empty.png │ └── todo-list-sample.png │ ├── persistiendo-nuestra-base-de-datos │ ├── index.md │ ├── items-added.png │ └── sistemas-de-archivos-de-un-contenedor.png │ ├── que-sigue │ └── index.md │ ├── usando-bind-mounts │ ├── index.md │ └── updated-add-button.png │ └── usando-docker-compose │ └── index.md ├── docs_pt-br ├── css │ ├── dark-mode.css │ └── styles.css ├── dicas-pwd │ ├── editor-button.png │ ├── editor-display.png │ └── index.md ├── fonts │ └── hinted-Geomanist-Book.ttf ├── images │ ├── docker-labs-logo.svg │ └── pwd-badge.png ├── index.md ├── js │ ├── custom.js │ ├── jquery.raptorize.1.0.js │ ├── raptor-sound.mp3 │ ├── raptor-sound.ogg │ └── raptor.png └── tutorial │ ├── aplicacoes-multi-conteiner │ ├── index.md │ └── multi-app-architecture.png │ ├── atualizando-nossa-aplicacao │ ├── index.md │ └── todo-list-updated-empty-text.png │ ├── compartilhando-nossa-aplicacao │ ├── index.md │ └── push-command.png │ ├── e-depois │ └── index.md │ ├── index.md │ ├── melhores-praticas-construir-imagem │ └── index.md │ ├── nossa-aplicacao │ ├── index.md │ ├── todo-list-empty.png │ └── todo-list-sample.png │ ├── persistindo-nossos-dados │ ├── index.md │ └── items-added.png │ ├── utilizando-docker-compose │ └── index.md │ └── utilizando-montagens-bind │ ├── index.md │ └── updated-add-button.png ├── mkdocs-config.json ├── mkdocs.yml ├── requirements.txt └── verify.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Install the base requirements for the app. 2 | # This stage is to support development. 3 | FROM python:alpine AS base 4 | WORKDIR /app 5 | COPY requirements.txt . 6 | RUN pip install -r requirements.txt 7 | 8 | # Create the zip download file 9 | FROM node:alpine AS app-zip-creator 10 | WORKDIR /app 11 | COPY app . 12 | RUN rm -rf node_modules && \ 13 | apk add zip && \ 14 | zip -r /app.zip /app 15 | 16 | # Configure the mkdocs.yml file for the correct language 17 | FROM node:alpine AS mkdoc-config-builder 18 | WORKDIR /app 19 | RUN yarn init -y && yarn add yaml 20 | COPY configure.js mkdocs* ./ 21 | ARG LANGUAGE 22 | RUN node configure.js $LANGUAGE 23 | 24 | # Dev-ready container - have to put configured file at root to prevent mount from overwriting it 25 | FROM base AS dev 26 | COPY --from=mkdoc-config-builder /app/mkdocs-configured.yml / 27 | CMD ["mkdocs", "serve", "-a", "0.0.0.0:8000", "-f", "/mkdocs-configured.yml"] 28 | 29 | # Do the actual build of the mkdocs site 30 | FROM base AS build 31 | COPY . . 32 | COPY --from=mkdoc-config-builder /app/mkdocs-configured.yml ./mkdocs.yml 33 | ARG LANGUAGE 34 | RUN mv docs_${LANGUAGE} docs 35 | RUN mkdocs build 36 | 37 | # Extract the static content from the build 38 | # and use a nginx image to serve the content 39 | FROM nginx:alpine 40 | COPY --from=app-zip-creator /app.zip /usr/share/nginx/html/assets/app.zip 41 | COPY --from=build /app/site /usr/share/nginx/html 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker 101 Tutorial 2 | 3 | > :warning: **This project is archived.** The official "Getting Started" guide can be found at [docker/getting-started](https://github.com/docker/getting-started). 4 | 5 | This tutorial has been written with the intent of helping folks get up and running 6 | with containers. While not going too much into depth, it covers the following topics: 7 | 8 | - Running your first container 9 | - Building containers 10 | - Learning what containers are running and removing them 11 | - Using volumes to persist data 12 | - Using bind mounts to support development 13 | - Using container networking to support multi-container applications 14 | - Using Docker Compose to simplify the definition and sharing of applications 15 | - Using image layer caching to speed up builds and reduce push/pull size 16 | - Using multi-stage builds to separate build-time and runtime dependencies 17 | 18 | ## Getting Started 19 | 20 | If you wish to run the tutorial, you can use the following command: 21 | 22 | ```bash 23 | docker run -dp 80:80 dockersamples/101-tutorial 24 | ``` 25 | 26 | Once it has started, you can open your browser to [http://localhost](http://localhost) or 27 | port 80 if running on Play-with-Docker. 28 | 29 | 30 | ## Development 31 | 32 | This project has a `docker-compose.yml` file, which will start the mkdocs application on your 33 | local machine and help you see changes instantly. 34 | 35 | ```bash 36 | docker-compose up 37 | ``` 38 | 39 | By default, the dev container will use the English version of the tutorial. If you wish to work on 40 | a different version, modify the `services.docs.build.args.LANGUAGE` value to the language you want 41 | to work in. Note that the build will fail if the steps below (for new languages) haven't been 42 | completed yet. 43 | 44 | 45 | ## Contributing 46 | 47 | If you find typos or other issues with the tutorial, feel free to create a PR and suggest fixes! 48 | 49 | If you have ideas on how to make the tutorial better or new content, please open an issue first 50 | before working on your idea. While we love input, we want to keep the tutorial is scoped to new-comers. 51 | As such, we may reject ideas for more advanced requests and don't want you to lose any work you might 52 | have done. So, ask first and we'll gladly hear your thoughts! 53 | 54 | 55 | ### Translating the Tutorial 56 | 57 | If you wish to translate the tutorial into another language, you need to do the following: 58 | 59 | 1. Copy the `docs_en` directory and rename it as `docs_[your-language-code]`. 60 | 1. Translate each of the directories. 61 | 1. Translate all *.md files 62 | 1. In the `mkdocs-config.json`, add a key for `your-language-code` and fill in the 63 | remaining pieces to configure the mkdocs build. 64 | 1. To test everything out, you can run the `build.sh` script, which will verify the config file, 65 | as well as build all languages. 66 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "101-app", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "prettify": "prettier -l --write \"**/*.js\"", 8 | "test": "jest", 9 | "dev": "nodemon src/index.js" 10 | }, 11 | "dependencies": { 12 | "body-parser": "^1.19.0", 13 | "express": "^4.17.1", 14 | "mysql": "^2.17.1", 15 | "sqlite3": "^4.1.0", 16 | "uuid": "^3.3.3", 17 | "wait-port": "^0.2.2" 18 | }, 19 | "prettier": { 20 | "trailingComma": "all", 21 | "tabWidth": 4, 22 | "useTabs": false, 23 | "semi": true, 24 | "singleQuote": true 25 | }, 26 | "devDependencies": { 27 | "jest": "^24.9.0", 28 | "nodemon": "^1.19.2", 29 | "prettier": "^1.18.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/spec/persistence/sqlite.spec.js: -------------------------------------------------------------------------------- 1 | const db = require('../../src/persistence/sqlite'); 2 | const fs = require('fs'); 3 | 4 | const ITEM = { 5 | id: '7aef3d7c-d301-4846-8358-2a91ec9d6be3', 6 | name: 'Test', 7 | completed: false, 8 | }; 9 | 10 | beforeEach(() => { 11 | if (fs.existsSync('/etc/todos/todo.db')) { 12 | fs.unlinkSync('/etc/todos/todo.db'); 13 | } 14 | }); 15 | 16 | test('it initializes correctly', async () => { 17 | await db.init(); 18 | }); 19 | 20 | test('it can store and retrieve items', async () => { 21 | await db.init(); 22 | 23 | await db.storeItem(ITEM); 24 | 25 | const items = await db.getItems(); 26 | expect(items.length).toBe(1); 27 | expect(items[0]).toEqual(ITEM); 28 | }); 29 | 30 | test('it can update an existing item', async () => { 31 | await db.init(); 32 | 33 | const initialItems = await db.getItems(); 34 | expect(initialItems.length).toBe(0); 35 | 36 | await db.storeItem(ITEM); 37 | 38 | await db.updateItem( 39 | ITEM.id, 40 | Object.assign({}, ITEM, { completed: !ITEM.completed }), 41 | ); 42 | 43 | const items = await db.getItems(); 44 | expect(items.length).toBe(1); 45 | expect(items[0].completed).toBe(!ITEM.completed); 46 | }); 47 | 48 | test('it can remove an existing item', async () => { 49 | await db.init(); 50 | await db.storeItem(ITEM); 51 | 52 | await db.removeItem(ITEM.id); 53 | 54 | const items = await db.getItems(); 55 | expect(items.length).toBe(0); 56 | }); 57 | 58 | test('it can get a single item', async () => { 59 | await db.init(); 60 | await db.storeItem(ITEM); 61 | 62 | const item = await db.getItem(ITEM.id); 63 | expect(item).toEqual(ITEM); 64 | }); 65 | -------------------------------------------------------------------------------- /app/spec/routes/addItem.spec.js: -------------------------------------------------------------------------------- 1 | const db = require('../../src/persistence'); 2 | const addItem = require('../../src/routes/addItem'); 3 | const ITEM = { id: 12345 }; 4 | const uuid = require('uuid/v4'); 5 | 6 | jest.mock('uuid/v4', () => jest.fn()); 7 | 8 | jest.mock('../../src/persistence', () => ({ 9 | removeItem: jest.fn(), 10 | storeItem: jest.fn(), 11 | getItem: jest.fn(), 12 | })); 13 | 14 | test('it stores item correctly', async () => { 15 | const id = 'something-not-a-uuid'; 16 | const name = 'A sample item'; 17 | const req = { body: { name } }; 18 | const res = { send: jest.fn() }; 19 | 20 | uuid.mockReturnValue(id); 21 | 22 | await addItem(req, res); 23 | 24 | const expectedItem = { id, name, completed: false }; 25 | 26 | expect(db.storeItem.mock.calls.length).toBe(1); 27 | expect(db.storeItem.mock.calls[0][0]).toEqual(expectedItem); 28 | expect(res.send.mock.calls[0].length).toBe(1); 29 | expect(res.send.mock.calls[0][0]).toEqual(expectedItem); 30 | }); 31 | -------------------------------------------------------------------------------- /app/spec/routes/deleteItem.spec.js: -------------------------------------------------------------------------------- 1 | const db = require('../../src/persistence'); 2 | const deleteItem = require('../../src/routes/deleteItem'); 3 | const ITEM = { id: 12345 }; 4 | 5 | jest.mock('../../src/persistence', () => ({ 6 | removeItem: jest.fn(), 7 | getItem: jest.fn(), 8 | })); 9 | 10 | test('it removes item correctly', async () => { 11 | const req = { params: { id: 12345 } }; 12 | const res = { sendStatus: jest.fn() }; 13 | 14 | await deleteItem(req, res); 15 | 16 | expect(db.removeItem.mock.calls.length).toBe(1); 17 | expect(db.removeItem.mock.calls[0][0]).toBe(req.params.id); 18 | expect(res.sendStatus.mock.calls[0].length).toBe(1); 19 | expect(res.sendStatus.mock.calls[0][0]).toBe(200); 20 | }); 21 | -------------------------------------------------------------------------------- /app/spec/routes/getItems.spec.js: -------------------------------------------------------------------------------- 1 | const db = require('../../src/persistence'); 2 | const getItems = require('../../src/routes/getItems'); 3 | const ITEMS = [{ id: 12345 }]; 4 | 5 | jest.mock('../../src/persistence', () => ({ 6 | getItems: jest.fn(), 7 | })); 8 | 9 | test('it gets items correctly', async () => { 10 | const req = {}; 11 | const res = { send: jest.fn() }; 12 | db.getItems.mockReturnValue(Promise.resolve(ITEMS)); 13 | 14 | await getItems(req, res); 15 | 16 | expect(db.getItems.mock.calls.length).toBe(1); 17 | expect(res.send.mock.calls[0].length).toBe(1); 18 | expect(res.send.mock.calls[0][0]).toEqual(ITEMS); 19 | }); 20 | -------------------------------------------------------------------------------- /app/spec/routes/updateItem.spec.js: -------------------------------------------------------------------------------- 1 | const db = require('../../src/persistence'); 2 | const updateItem = require('../../src/routes/updateItem'); 3 | const ITEM = { id: 12345 }; 4 | 5 | jest.mock('../../src/persistence', () => ({ 6 | getItem: jest.fn(), 7 | updateItem: jest.fn(), 8 | })); 9 | 10 | test('it updates items correctly', async () => { 11 | const req = { 12 | params: { id: 1234 }, 13 | body: { name: 'New title', completed: false }, 14 | }; 15 | const res = { send: jest.fn() }; 16 | 17 | db.getItem.mockReturnValue(Promise.resolve(ITEM)); 18 | 19 | await updateItem(req, res); 20 | 21 | expect(db.updateItem.mock.calls.length).toBe(1); 22 | expect(db.updateItem.mock.calls[0][0]).toBe(req.params.id); 23 | expect(db.updateItem.mock.calls[0][1]).toEqual({ 24 | name: 'New title', 25 | completed: false, 26 | }); 27 | 28 | expect(db.getItem.mock.calls.length).toBe(1); 29 | expect(db.getItem.mock.calls[0][0]).toBe(req.params.id); 30 | 31 | expect(res.send.mock.calls[0].length).toBe(1); 32 | expect(res.send.mock.calls[0][0]).toEqual(ITEM); 33 | }); 34 | -------------------------------------------------------------------------------- /app/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const db = require('./persistence'); 4 | const getItems = require('./routes/getItems'); 5 | const addItem = require('./routes/addItem'); 6 | const updateItem = require('./routes/updateItem'); 7 | const deleteItem = require('./routes/deleteItem'); 8 | 9 | app.use(require('body-parser').json()); 10 | app.use(express.static(__dirname + '/static')); 11 | 12 | app.get('/items', getItems); 13 | app.post('/items', addItem); 14 | app.put('/items/:id', updateItem); 15 | app.delete('/items/:id', deleteItem); 16 | 17 | db.init().then(() => { 18 | app.listen(3000, () => console.log('Listening on port 3000')); 19 | }).catch((err) => { 20 | console.error(err); 21 | process.exit(1); 22 | }); 23 | 24 | const gracefulShutdown = () => { 25 | db.teardown() 26 | .catch(() => {}) 27 | .then(() => process.exit()); 28 | }; 29 | 30 | process.on('SIGINT', gracefulShutdown); 31 | process.on('SIGTERM', gracefulShutdown); 32 | process.on('SIGUSR2', gracefulShutdown); // Sent by nodemon 33 | -------------------------------------------------------------------------------- /app/src/persistence/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.MYSQL_HOST) module.exports = require('./mysql'); 2 | else module.exports = require('./sqlite'); 3 | -------------------------------------------------------------------------------- /app/src/persistence/mysql.js: -------------------------------------------------------------------------------- 1 | const waitPort = require('wait-port'); 2 | const fs = require('fs'); 3 | const mysql = require('mysql'); 4 | 5 | const { 6 | MYSQL_HOST: HOST, 7 | MYSQL_HOST_FILE: HOST_FILE, 8 | MYSQL_USER: USER, 9 | MYSQL_USER_FILE: USER_FILE, 10 | MYSQL_PASSWORD: PASSWORD, 11 | MYSQL_PASSWORD_FILE: PASSWORD_FILE, 12 | MYSQL_DB: DB, 13 | MYSQL_DB_FILE: DB_FILE, 14 | } = process.env; 15 | 16 | let pool; 17 | 18 | async function init() { 19 | const host = HOST_FILE ? fs.readFileSync(HOST_FILE) : HOST; 20 | const user = USER_FILE ? fs.readFileSync(USER_FILE) : USER; 21 | const password = PASSWORD_FILE ? fs.readFileSync(PASSWORD_FILE) : PASSWORD; 22 | const database = DB_FILE ? fs.readFileSync(DB_FILE) : DB; 23 | 24 | await waitPort({ host, port : 3306, timeout: 15000 }); 25 | 26 | pool = mysql.createPool({ 27 | connectionLimit: 5, 28 | host, 29 | user, 30 | password, 31 | database, 32 | }); 33 | 34 | return new Promise((acc, rej) => { 35 | pool.query( 36 | 'CREATE TABLE IF NOT EXISTS todo_items (id varchar(36), name varchar(255), completed boolean)', 37 | err => { 38 | if (err) return rej(err); 39 | 40 | console.log(`Connected to mysql db at host ${HOST}`); 41 | acc(); 42 | }, 43 | ); 44 | }); 45 | } 46 | 47 | async function teardown() { 48 | return new Promise((acc, rej) => { 49 | pool.end(err => { 50 | if (err) rej(err); 51 | else acc(); 52 | }); 53 | }); 54 | } 55 | 56 | async function getItems() { 57 | return new Promise((acc, rej) => { 58 | pool.query('SELECT * FROM todo_items', (err, rows) => { 59 | if (err) return rej(err); 60 | acc( 61 | rows.map(item => 62 | Object.assign({}, item, { 63 | completed: item.completed === 1, 64 | }), 65 | ), 66 | ); 67 | }); 68 | }); 69 | } 70 | 71 | async function getItem(id) { 72 | return new Promise((acc, rej) => { 73 | pool.query('SELECT * FROM todo_items WHERE id=?', [id], (err, rows) => { 74 | if (err) return rej(err); 75 | acc( 76 | rows.map(item => 77 | Object.assign({}, item, { 78 | completed: item.completed === 1, 79 | }), 80 | )[0], 81 | ); 82 | }); 83 | }); 84 | } 85 | 86 | async function storeItem(item) { 87 | return new Promise((acc, rej) => { 88 | pool.query( 89 | 'INSERT INTO todo_items (id, name, completed) VALUES (?, ?, ?)', 90 | [item.id, item.name, item.completed ? 1 : 0], 91 | err => { 92 | if (err) return rej(err); 93 | acc(); 94 | }, 95 | ); 96 | }); 97 | } 98 | 99 | async function updateItem(id, item) { 100 | return new Promise((acc, rej) => { 101 | pool.query( 102 | 'UPDATE todo_items SET name=?, completed=? WHERE id=?', 103 | [item.name, item.completed ? 1 : 0, id], 104 | err => { 105 | if (err) return rej(err); 106 | acc(); 107 | }, 108 | ); 109 | }); 110 | } 111 | 112 | async function removeItem(id) { 113 | return new Promise((acc, rej) => { 114 | pool.query('DELETE FROM todo_items WHERE id = ?', [id], err => { 115 | if (err) return rej(err); 116 | acc(); 117 | }); 118 | }); 119 | } 120 | 121 | module.exports = { 122 | init, 123 | teardown, 124 | getItems, 125 | getItem, 126 | storeItem, 127 | updateItem, 128 | removeItem, 129 | }; 130 | -------------------------------------------------------------------------------- /app/src/persistence/sqlite.js: -------------------------------------------------------------------------------- 1 | const sqlite3 = require('sqlite3').verbose(); 2 | const fs = require('fs'); 3 | const location = process.env.SQLITE_DB_LOCATION || '/etc/todos/todo.db'; 4 | 5 | let db, dbAll, dbRun; 6 | 7 | function init() { 8 | const dirName = require('path').dirname(location); 9 | if (!fs.existsSync(dirName)) { 10 | fs.mkdirSync(dirName, { recursive: true }); 11 | } 12 | 13 | return new Promise((acc, rej) => { 14 | db = new sqlite3.Database(location, err => { 15 | if (err) return rej(err); 16 | 17 | if (process.env.NODE_ENV !== 'test') 18 | console.log(`Using sqlite database at ${location}`); 19 | 20 | db.run( 21 | 'CREATE TABLE IF NOT EXISTS todo_items (id varchar(36), name varchar(255), completed boolean)', 22 | (err, result) => { 23 | if (err) return rej(err); 24 | acc(); 25 | }, 26 | ); 27 | }); 28 | }); 29 | } 30 | 31 | async function teardown() { 32 | return new Promise((acc, rej) => { 33 | db.close(err => { 34 | if (err) rej(err); 35 | else acc(); 36 | }); 37 | }); 38 | } 39 | 40 | async function getItems() { 41 | return new Promise((acc, rej) => { 42 | db.all('SELECT * FROM todo_items', (err, rows) => { 43 | if (err) return rej(err); 44 | acc( 45 | rows.map(item => 46 | Object.assign({}, item, { 47 | completed: item.completed === 1, 48 | }), 49 | ), 50 | ); 51 | }); 52 | }); 53 | } 54 | 55 | async function getItem(id) { 56 | return new Promise((acc, rej) => { 57 | db.all('SELECT * FROM todo_items WHERE id=?', [id], (err, rows) => { 58 | if (err) return rej(err); 59 | acc( 60 | rows.map(item => 61 | Object.assign({}, item, { 62 | completed: item.completed === 1, 63 | }), 64 | )[0], 65 | ); 66 | }); 67 | }); 68 | } 69 | 70 | async function storeItem(item) { 71 | return new Promise((acc, rej) => { 72 | db.run( 73 | 'INSERT INTO todo_items (id, name, completed) VALUES (?, ?, ?)', 74 | [item.id, item.name, item.completed ? 1 : 0], 75 | err => { 76 | if (err) return rej(err); 77 | acc(); 78 | }, 79 | ); 80 | }); 81 | } 82 | 83 | async function updateItem(id, item) { 84 | return new Promise((acc, rej) => { 85 | db.run( 86 | 'UPDATE todo_items SET name=?, completed=? WHERE id = ?', 87 | [item.name, item.completed ? 1 : 0, id], 88 | err => { 89 | if (err) return rej(err); 90 | acc(); 91 | }, 92 | ); 93 | }); 94 | } 95 | 96 | async function removeItem(id) { 97 | return new Promise((acc, rej) => { 98 | db.run('DELETE FROM todo_items WHERE id = ?', [id], err => { 99 | if (err) return rej(err); 100 | acc(); 101 | }); 102 | }); 103 | } 104 | 105 | module.exports = { 106 | init, 107 | teardown, 108 | getItems, 109 | getItem, 110 | storeItem, 111 | updateItem, 112 | removeItem, 113 | }; 114 | -------------------------------------------------------------------------------- /app/src/routes/addItem.js: -------------------------------------------------------------------------------- 1 | const db = require('../persistence'); 2 | const uuid = require('uuid/v4'); 3 | 4 | module.exports = async (req, res) => { 5 | const item = { 6 | id: uuid(), 7 | name: req.body.name, 8 | completed: false, 9 | }; 10 | 11 | await db.storeItem(item); 12 | res.send(item); 13 | }; 14 | -------------------------------------------------------------------------------- /app/src/routes/deleteItem.js: -------------------------------------------------------------------------------- 1 | const db = require('../persistence'); 2 | 3 | module.exports = async (req, res) => { 4 | await db.removeItem(req.params.id); 5 | res.sendStatus(200); 6 | }; 7 | -------------------------------------------------------------------------------- /app/src/routes/getItems.js: -------------------------------------------------------------------------------- 1 | const db = require('../persistence'); 2 | 3 | module.exports = async (req, res) => { 4 | const items = await db.getItems(); 5 | res.send(items); 6 | }; 7 | -------------------------------------------------------------------------------- /app/src/routes/updateItem.js: -------------------------------------------------------------------------------- 1 | const db = require('../persistence'); 2 | 3 | module.exports = async (req, res) => { 4 | await db.updateItem(req.params.id, { 5 | name: req.body.name, 6 | completed: req.body.completed, 7 | }); 8 | const item = await db.getItem(req.params.id); 9 | res.send(item); 10 | }; 11 | -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-brands-400.eot -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-brands-400.ttf -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-brands-400.woff -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-brands-400.woff2 -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-regular-400.eot -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-regular-400.ttf -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-regular-400.woff -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-regular-400.woff2 -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-solid-900.eot -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-solid-900.ttf -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-solid-900.woff -------------------------------------------------------------------------------- /app/src/static/css/font-awesome/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/app/src/static/css/font-awesome/fa-solid-900.woff2 -------------------------------------------------------------------------------- /app/src/static/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f4f4f4; 3 | margin-top: 50px; 4 | font-family: 'Lato'; 5 | } 6 | 7 | .item { 8 | background-color: white; 9 | padding: 15px; 10 | margin-bottom: 15px; 11 | border: transparent; 12 | border-radius: 5px; 13 | box-shadow: 0 0 1em #ccc; 14 | transition: all .2s ease-in-out; 15 | } 16 | 17 | .item:hover { 18 | box-shadow: 0 0 1em #aaa; 19 | } 20 | 21 | .item.completed { 22 | text-decoration: line-through; 23 | } 24 | 25 | .toggles { 26 | color: black; 27 | } 28 | 29 | .name { 30 | padding-top: 3px; 31 | } 32 | 33 | .remove { 34 | padding-left: 0; 35 | } 36 | 37 | button:focus { 38 | border: 1px solid #333; 39 | } -------------------------------------------------------------------------------- /app/src/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Todo App 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/static/js/app.js: -------------------------------------------------------------------------------- 1 | function App() { 2 | const { Container, Row, Col } = ReactBootstrap; 3 | return ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | function TodoListCard() { 15 | const [items, setItems] = React.useState(null); 16 | 17 | React.useEffect(() => { 18 | fetch('/items') 19 | .then(r => r.json()) 20 | .then(setItems); 21 | }, []); 22 | 23 | const onNewItem = React.useCallback( 24 | newItem => { 25 | setItems([...items, newItem]); 26 | }, 27 | [items], 28 | ); 29 | 30 | const onItemUpdate = React.useCallback( 31 | item => { 32 | const index = items.findIndex(i => i.id === item.id); 33 | setItems([ 34 | ...items.slice(0, index), 35 | item, 36 | ...items.slice(index + 1), 37 | ]); 38 | }, 39 | [items], 40 | ); 41 | 42 | const onItemRemoval = React.useCallback( 43 | item => { 44 | const index = items.findIndex(i => i.id === item.id); 45 | setItems([...items.slice(0, index), ...items.slice(index + 1)]); 46 | }, 47 | [items], 48 | ); 49 | 50 | if (items === null) return 'Loading...'; 51 | 52 | return ( 53 | 54 | 55 | {items.length === 0 && ( 56 |

No items yet! Add one above!

57 | )} 58 | {items.map(item => ( 59 | 65 | ))} 66 |
67 | ); 68 | } 69 | 70 | function AddItemForm({ onNewItem }) { 71 | const { Form, InputGroup, Button } = ReactBootstrap; 72 | 73 | const [newItem, setNewItem] = React.useState(''); 74 | const [submitting, setSubmitting] = React.useState(false); 75 | 76 | const submitNewItem = e => { 77 | e.preventDefault(); 78 | setSubmitting(true); 79 | fetch('/items', { 80 | method: 'POST', 81 | body: JSON.stringify({ name: newItem }), 82 | headers: { 'Content-Type': 'application/json' }, 83 | }) 84 | .then(r => r.json()) 85 | .then(item => { 86 | onNewItem(item); 87 | setSubmitting(false); 88 | setNewItem(''); 89 | }); 90 | }; 91 | 92 | return ( 93 |
94 | 95 | setNewItem(e.target.value)} 98 | type="text" 99 | placeholder="New Item" 100 | aria-describedby="basic-addon1" 101 | /> 102 | 103 | 111 | 112 | 113 |
114 | ); 115 | } 116 | 117 | function ItemDisplay({ item, onItemUpdate, onItemRemoval }) { 118 | const { Container, Row, Col, Button } = ReactBootstrap; 119 | 120 | const toggleCompletion = () => { 121 | fetch(`/items/${item.id}`, { 122 | method: 'PUT', 123 | body: JSON.stringify({ 124 | name: item.name, 125 | completed: !item.completed, 126 | }), 127 | headers: { 'Content-Type': 'application/json' }, 128 | }) 129 | .then(r => r.json()) 130 | .then(onItemUpdate); 131 | }; 132 | 133 | const removeItem = () => { 134 | fetch(`/items/${item.id}`, { method: 'DELETE' }).then(() => 135 | onItemRemoval(item), 136 | ); 137 | }; 138 | 139 | return ( 140 | 141 | 142 | 143 | 161 | 162 | 163 | {item.name} 164 | 165 | 166 | 174 | 175 | 176 | 177 | ); 178 | } 179 | 180 | ReactDOM.render(, document.getElementById('root')); 181 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Quick verify of the mkdocs config 6 | echo "Verifying mkdocs config" 7 | docker run --rm -tiv $PWD:/app -w /app node:alpine node verify.js 8 | 9 | # Validate tests are passing for the app 10 | echo "Running tests for the app" 11 | docker run --rm -tiv $PWD/app:/app -w /app node:alpine sh -c "yarn install && yarn test" 12 | 13 | if [ $1 == "--push" ]; then 14 | WILL_PUSH=1 15 | else 16 | WILL_PUSH=0 17 | fi 18 | 19 | # Build each language and, you know, multi-arch it! 20 | for language in `find . -type d -maxdepth 1 | grep docs | cut -d '_' -f 2`; do 21 | echo "Going to build image for $language" 22 | 23 | docker buildx build \ 24 | --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \ 25 | --build-arg LANGUAGE=$language \ 26 | -t dockersamples/101-tutorial:$language \ 27 | $( (( $WILL_PUSH == 1 )) && printf %s '--push' ) . 28 | done 29 | 30 | # Retag "en" as latest 31 | docker buildx build \ 32 | --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \ 33 | --build-arg LANGUAGE=en \ 34 | -t dockersamples/101-tutorial:latest \ 35 | $( (( $WILL_PUSH == 1 )) && printf %s '--push' ) . 36 | -------------------------------------------------------------------------------- /configure.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const YAML = require('yaml'); 3 | const { stringifyString } = require('yaml/util'); 4 | 5 | const language = process.argv[2]; 6 | console.log(`Configuring mkdocs for ${language}`); 7 | 8 | const languageData = require('./mkdocs-config.json')[language]; 9 | if (!languageData) { 10 | console.error("Unable to find language config"); 11 | process.exit(1); 12 | } 13 | 14 | const mkdocData = YAML.parse(fs.readFileSync('mkdocs.yml').toString()); 15 | mkdocData['docs_dir'] = `/app/docs_${language}`; 16 | mkdocData['site_description'] = languageData.site_description; 17 | mkdocData.theme.language = languageData.language_code; 18 | 19 | mkdocData.nav = [ 20 | { 21 | [languageData.nav.Tutorial.title] : [ 22 | { [languageData.nav['Getting Started'].title] : `${languageData.tutorial_dir_name}/index.md`, }, 23 | { [languageData.nav['Our Application'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Our Application'].dir_name}/index.md` }, 24 | { [languageData.nav['Updating our App'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Updating our App'].dir_name}/index.md` }, 25 | { [languageData.nav['Sharing our App'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Sharing our App'].dir_name}/index.md` }, 26 | { [languageData.nav['Persisting our DB'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Persisting our DB'].dir_name}/index.md` }, 27 | { [languageData.nav['Using Bind Mounts'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Using Bind Mounts'].dir_name}/index.md` }, 28 | { [languageData.nav['Multi-Container Apps'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Multi-Container Apps'].dir_name}/index.md` }, 29 | { [languageData.nav['Using Docker Compose'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Using Docker Compose'].dir_name}/index.md` }, 30 | { [languageData.nav['Image Building Best Practices'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['Image Building Best Practices'].dir_name}/index.md` }, 31 | { [languageData.nav['What Next?'].title] : `${languageData.tutorial_dir_name}/${languageData.nav['What Next?'].dir_name}/index.md` }, 32 | ], 33 | }, 34 | { 35 | [languageData.nav['PWD Tips'].title] : `${languageData.nav['PWD Tips'].dir_name}/index.md` 36 | }, 37 | ]; 38 | 39 | fs.writeFileSync("mkdocs-configured.yml", YAML.stringify(mkdocData)); 40 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | docs: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | target: dev 9 | args: 10 | LANGUAGE: en 11 | ports: 12 | - 8000:8000 13 | volumes: 14 | - ./:/app 15 | -------------------------------------------------------------------------------- /docs_en/css/dark-mode.css: -------------------------------------------------------------------------------- 1 | @media (prefers-color-scheme: dark) { 2 | .md-main { 3 | color: rgba(255, 255, 255, 0.75) !important; 4 | background-color: #36393e !important; 5 | } 6 | 7 | article img { 8 | box-shadow: 0 0 1em #000; 9 | } 10 | 11 | .md-main h1 { 12 | color: rgba(255, 255, 255, 0.8) !important; 13 | } 14 | blockquote { 15 | color: rgba(255, 255, 255, 0.75) !important; 16 | } 17 | table { 18 | background-color: #616161 !important; 19 | } 20 | tbody { 21 | background-color: #484848 !important; 22 | } 23 | .md-sidebar__scrollwrap::-webkit-scrollbar-thumb { 24 | background-color: #e0e0e0 !important; 25 | } 26 | .md-nav { 27 | color: rgba(255, 255, 255, 0.8) !important; 28 | background-color: #36393e !important; 29 | } 30 | html .md-nav--primary .md-nav__title:before { 31 | color: #fafafa !important; 32 | } 33 | .md-nav__title { 34 | color: rgba(255, 255, 255, 0.9) !important; 35 | background-color: #36393e !important; 36 | } 37 | .md-nav--primary .md-nav__link:after { 38 | color: #fafafa !important; 39 | } 40 | .md-nav__list { 41 | color: rgba(255, 255, 255, 0.8) !important; 42 | background-color: #36393e !important; 43 | } 44 | .md-nav__item { 45 | color: rgba(255, 255, 255, 0.7) !important; 46 | background-color: #36393e !important; 47 | } 48 | .md-search__scrollwrap::-webkit-scrollbar-thumb { 49 | background-color: #e0e0e0 !important; 50 | } 51 | .md-search__scrollwrap { 52 | background-color: #44484e !important; 53 | } 54 | .md-search-result__article--document:before { 55 | color: #eee !important; 56 | } 57 | .md-search-result__list { 58 | color: #eee !important; 59 | background-color: #36393e !important; 60 | } 61 | .md-search-result__meta { 62 | background-color: #eee !important; 63 | } 64 | .md-search-result__teaser { 65 | color: #bdbdbd !important; 66 | } 67 | .md-typeset code { 68 | color: white !important; 69 | /* box-shadow: 0.29412em 0 0 hsla(0, 0%, 100%, 0.07), 70 | -0.29412em 0 0 hsla(0, 0%, 100%, 0.1);*/ 71 | } 72 | .md-typeset a code { 73 | color: #94acff !important; 74 | } 75 | .md-typeset a:hover code { 76 | text-decoration: underline; 77 | } 78 | .linenos { 79 | color: #f5f5f5 !important; 80 | background-color: #313131 !important; 81 | } 82 | .codehilite { 83 | background-color: #44484e !important; 84 | } 85 | .md-typeset .codehilite::-webkit-scrollbar { 86 | height: 1rem !important; 87 | } 88 | .codehilite pre { 89 | color: #fafafa !important; 90 | background-color: transparent !important; 91 | } 92 | .codehilite .hll { 93 | background-color: #272822 !important; 94 | } 95 | .codehilite .c { 96 | color: #8a8f98 !important; 97 | } 98 | .codehilite .err { 99 | color: #960050 !important; 100 | background-color: #1e0010 !important; 101 | } 102 | .codehilite .k { 103 | color: #66d9ef !important; 104 | } 105 | .codehilite .l { 106 | color: #ae81ff !important; 107 | } 108 | .codehilite .n { 109 | color: #f8f8f2 !important; 110 | } 111 | .codehilite .o { 112 | color: #f92672 !important; 113 | } 114 | .codehilite .p { 115 | color: #f8f8f2 !important; 116 | } 117 | .codehilite .cm { 118 | color: #8a8f98 !important; 119 | } 120 | .codehilite .cp { 121 | color: #8a8f98 !important; 122 | } 123 | .codehilite .c1 { 124 | color: #8a8f98 !important; 125 | } 126 | .codehilite .cs { 127 | color: #8a8f98 !important; 128 | } 129 | .codehilite .ge { 130 | font-style: italic !important; 131 | } 132 | .codehilite .gs { 133 | font-weight: bold !important; 134 | } 135 | .codehilite .kc { 136 | color: #66d9ef !important; 137 | } 138 | .codehilite .kd { 139 | color: #66d9ef !important; 140 | } 141 | .codehilite .kn { 142 | color: #f92672 !important; 143 | } 144 | .codehilite .kp { 145 | color: #66d9ef !important; 146 | } 147 | .codehilite .kr { 148 | color: #66d9ef !important; 149 | } 150 | .codehilite .kt { 151 | color: #66d9ef !important; 152 | } 153 | .codehilite .ld { 154 | color: #e6db74 !important; 155 | } 156 | .codehilite .m { 157 | color: #ae81ff !important; 158 | } 159 | .codehilite .s { 160 | color: #e6db74 !important; 161 | } 162 | .codehilite .na { 163 | color: #a6e22e !important; 164 | } 165 | .codehilite .nb { 166 | color: #f8f8f2 !important; 167 | } 168 | .codehilite .nc { 169 | color: #a6e22e !important; 170 | } 171 | .codehilite .no { 172 | color: #66d9ef !important; 173 | } 174 | .codehilite .nd { 175 | color: #a6e22e !important; 176 | } 177 | .codehilite .ni { 178 | color: #f8f8f2 !important; 179 | } 180 | .codehilite .ne { 181 | color: #a6e22e !important; 182 | } 183 | .codehilite .nf { 184 | color: #a6e22e !important; 185 | } 186 | .codehilite .nl { 187 | color: #f8f8f2 !important; 188 | } 189 | .codehilite .nn { 190 | color: #f8f8f2 !important; 191 | } 192 | .codehilite .nx { 193 | color: #a6e22e !important; 194 | } 195 | .codehilite .py { 196 | color: #f8f8f2 !important; 197 | } 198 | .codehilite .nt { 199 | color: #f92672 !important; 200 | } 201 | .codehilite .nv { 202 | color: #f8f8f2 !important; 203 | } 204 | .codehilite .ow { 205 | color: #f92672 !important; 206 | } 207 | .codehilite .w { 208 | color: #f8f8f2 !important; 209 | } 210 | .codehilite .mf { 211 | color: #ae81ff !important; 212 | } 213 | .codehilite .mh { 214 | color: #ae81ff !important; 215 | } 216 | .codehilite .mi { 217 | color: #ae81ff !important; 218 | } 219 | .codehilite .mo { 220 | color: #ae81ff !important; 221 | } 222 | .codehilite .sb { 223 | color: #e6db74 !important; 224 | } 225 | .codehilite .sc { 226 | color: #e6db74 !important; 227 | } 228 | .codehilite .sd { 229 | color: #e6db74 !important; 230 | } 231 | .codehilite .s2 { 232 | color: #e6db74 !important; 233 | } 234 | .codehilite .se { 235 | color: #ae81ff !important; 236 | } 237 | .codehilite .sh { 238 | color: #e6db74 !important; 239 | } 240 | .codehilite .si { 241 | color: #e6db74 !important; 242 | } 243 | .codehilite .sx { 244 | color: #e6db74 !important; 245 | } 246 | .codehilite .sr { 247 | color: #e6db74 !important; 248 | } 249 | .codehilite .s1 { 250 | color: #e6db74 !important; 251 | } 252 | .codehilite .ss { 253 | color: #e6db74 !important; 254 | } 255 | .codehilite .bp { 256 | color: #f8f8f2 !important; 257 | } 258 | .codehilite .vc { 259 | color: #f8f8f2 !important; 260 | } 261 | .codehilite .vg { 262 | color: #f8f8f2 !important; 263 | } 264 | .codehilite .vi { 265 | color: #f8f8f2 !important; 266 | } 267 | .codehilite .il { 268 | color: #ae81ff !important; 269 | } 270 | .codehilite .gu { 271 | color: #8a8f98 !important; 272 | } 273 | .codehilite .gd { 274 | color: #9c1042 !important; 275 | background-color: #eaa; 276 | } 277 | .codehilite .gi { 278 | color: #364c0a !important; 279 | background-color: #91e891; 280 | } 281 | .md-clipboard:before { 282 | color: rgba(255, 255, 255, 0.31); 283 | } 284 | .codehilite:hover .md-clipboard:before, .md-typeset .highlight:hover .md-clipboard:before, pre:hover .md-clipboard:before { 285 | color: rgba(255, 255, 255, 0.6); 286 | } 287 | .md-typeset summary:after { 288 | color: rgba(255, 255, 255, 0.26); 289 | } 290 | .md-typeset .admonition.example > .admonition-title, .md-typeset .admonition.example > summary, .md-typeset details.example > .admonition-title, .md-typeset details.example > summary { 291 | background-color: rgba(154, 109, 255, 0.21); 292 | } 293 | .md-nav__link[data-md-state='blur'] { 294 | color: #aec0ff; 295 | } 296 | .md-typeset .footnote { 297 | color: #888484 !important; 298 | } 299 | .md-typeset .footnote-ref:before { 300 | border-color: #888484 !important; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /docs_en/css/styles.css: -------------------------------------------------------------------------------- 1 | .text-center { 2 | text-align: center; 3 | } 4 | 5 | article img { 6 | border: 1px solid #eee; 7 | box-shadow: 0 0 1em #ccc; 8 | margin: 30px auto 10px; 9 | } 10 | 11 | article img.emojione { 12 | border: none; 13 | box-shadow: none; 14 | margin: 0; 15 | } 16 | 17 | .md-footer-nav { 18 | background-color: rgba(0,0,0,.57); 19 | } 20 | 21 | 22 | /* Docker Branding */ 23 | .md-header { 24 | background-color: #0d9cec !important; 25 | } 26 | 27 | @font-face{ 28 | font-family: "Geomanist"; 29 | src: url("../fonts/hinted-Geomanist-Book.ttf") 30 | } 31 | 32 | body { 33 | font-family: "Open Sans", sans-serif; 34 | font-size: 15px; 35 | font-weight: normal; 36 | font-style: normal; 37 | font-stretch: normal; 38 | line-height: 1.5; 39 | letter-spacing: normal; 40 | color: #577482; 41 | } 42 | 43 | h1, h2, h3, h4, .md-footer-nav__inner, .md-header-nav__title, footer.md-footer { 44 | font-family: Geomanist; 45 | } 46 | 47 | .md-header-nav__title { 48 | line-height: 2.9rem; 49 | } 50 | 51 | .md-header-nav__button img { 52 | width: 145px; 53 | } -------------------------------------------------------------------------------- /docs_en/fonts/hinted-Geomanist-Book.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/fonts/hinted-Geomanist-Book.ttf -------------------------------------------------------------------------------- /docs_en/images/docker-labs-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | background 4 | 5 | 6 | 7 | Layer 1 8 | 9 | 10 | 11 | 12 | 13 | Labs 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs_en/images/pwd-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/images/pwd-badge.png -------------------------------------------------------------------------------- /docs_en/index.md: -------------------------------------------------------------------------------- 1 | redirect: /tutorial/ 2 | 3 | -------------------------------------------------------------------------------- /docs_en/js/custom.js: -------------------------------------------------------------------------------- 1 | $(window).load(function() { 2 | $('body').raptorize({ 3 | 'enterOn' : 'konami-code' 4 | }); 5 | }); -------------------------------------------------------------------------------- /docs_en/js/jquery.raptorize.1.0.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Raptorize Plugin 1.0 3 | * www.ZURB.com/playground 4 | * Copyright 2010, ZURB 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | 10 | (function($) { 11 | 12 | $.fn.raptorize = function(options) { 13 | 14 | //Yo' defaults 15 | var defaults = { 16 | enterOn: 'click', //timer, konami-code, click 17 | delayTime: 5000 //time before raptor attacks on timer mode 18 | }; 19 | 20 | //Extend those options 21 | var options = $.extend(defaults, options); 22 | 23 | return this.each(function() { 24 | 25 | var _this = $(this); 26 | var audioSupported = false; 27 | //Stupid Browser Checking which should be in jQuery Support 28 | if ($.browser.mozilla && $.browser.version.substr(0, 5) >= "1.9.2" || $.browser.webkit) { 29 | audioSupported = true; 30 | } 31 | 32 | //Raptor Vars 33 | var raptorImageMarkup = '' 34 | var raptorAudioMarkup = ''; 35 | var locked = false; 36 | 37 | //Append Raptor and Style 38 | $('body').append(raptorImageMarkup); 39 | if(audioSupported) { $('body').append(raptorAudioMarkup); } 40 | var raptor = $('#elRaptor').css({ 41 | "position":"fixed", 42 | "bottom": "-700px", 43 | "right" : "0", 44 | "display" : "block" 45 | }) 46 | 47 | // Animating Code 48 | function init() { 49 | locked = true; 50 | 51 | //Sound Hilarity 52 | if(audioSupported) { 53 | function playSound() { 54 | document.getElementById('elRaptorShriek').play(); 55 | } 56 | playSound(); 57 | } 58 | 59 | // Movement Hilarity 60 | raptor.animate({ 61 | "bottom" : "0" 62 | }, function() { 63 | $(this).animate({ 64 | "bottom" : "-130px" 65 | }, 100, function() { 66 | var offset = (($(this).position().left)+400); 67 | $(this).delay(300).animate({ 68 | "right" : offset 69 | }, 2200, function() { 70 | raptor = $('#elRaptor').css({ 71 | "bottom": "-700px", 72 | "right" : "0" 73 | }) 74 | locked = false; 75 | }) 76 | }); 77 | }); 78 | } 79 | 80 | 81 | //Determine Entrance 82 | if(options.enterOn == 'timer') { 83 | setTimeout(init, options.delayTime); 84 | } else if(options.enterOn == 'click') { 85 | _this.bind('click', function(e) { 86 | e.preventDefault(); 87 | if(!locked) { 88 | init(); 89 | } 90 | }) 91 | } else if(options.enterOn == 'konami-code'){ 92 | var kkeys = [], konami = "38,38,40,40,37,39,37,39,66,65"; 93 | $(window).bind("keydown.raptorz", function(e){ 94 | kkeys.push( e.keyCode ); 95 | if ( kkeys.toString().indexOf( konami ) >= 0 ) { 96 | init(); 97 | kkeys = []; 98 | // $(window).unbind('keydown.raptorz'); 99 | } 100 | }, true); 101 | 102 | } 103 | 104 | });//each call 105 | }//orbit plugin call 106 | })(jQuery); 107 | 108 | -------------------------------------------------------------------------------- /docs_en/js/raptor-sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/js/raptor-sound.mp3 -------------------------------------------------------------------------------- /docs_en/js/raptor-sound.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/js/raptor-sound.ogg -------------------------------------------------------------------------------- /docs_en/js/raptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/js/raptor.png -------------------------------------------------------------------------------- /docs_en/pwd-tips/editor-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/pwd-tips/editor-button.png -------------------------------------------------------------------------------- /docs_en/pwd-tips/editor-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/pwd-tips/editor-display.png -------------------------------------------------------------------------------- /docs_en/pwd-tips/index.md: -------------------------------------------------------------------------------- 1 | 2 | ## Creating Files 3 | 4 | If you're new to a Linux terminal, don't worry! The easiest way to create a file is to use the `touch` command. 5 | 6 | ```bash 7 | touch Dockerfile 8 | ``` 9 | 10 | If you run `ls`, you'll see that the file has been created. Once created, you can use the **Editing Files** tips below. 11 | 12 | 13 | ## Editing Files 14 | 15 | In PWD, you are welcome to use any CLI-based editor. However, we know many folks aren't as comfortable in the CLI. 16 | You are welcome to click on the "Editor" button to get a file manager view. 17 | 18 | ![Editor Button](editor-button.png){: style=width:50% } 19 | {:.text-center} 20 | 21 | After clicking the editor button, the file editor will open. Selecting a file will provide a web-based editor. 22 | 23 | ![Editor Display](editor-display.png){: style=width:75% } 24 | {: .text-center } 25 | 26 | 27 | ## Opening an App when the badge is gone 28 | 29 | If you have started a container, but the port badge doesn't come up, there is a way to still open the app. 30 | 31 | 1. First, validate the container is actually running and didn't just fail to start. Run `docker ps` and 32 | verify there is a port mapping in the `PORTS` section. 33 | 34 | ```bash 35 | $ docker ps 36 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 37 | 976e7066d705 docker-101 "docker-entrypoint.s…" 2 seconds ago Up 1 second 0.0.0.0:3000->3000/tcp xenodochial_ritchie 38 | ``` 39 | 40 | If the container failed to start, check the container logs and try to figure out what might be going on. 41 | 42 | 1. In PWD, find the `SSH` display. It should have a display that looks like `ssh ip172...`. Copy everything AFTER 43 | the `ssh` portion (`ip172....`). 44 | 45 | 1. Paste that into the web browser, but do NOT hit enter yet. Find the `@` symbol. Replace it with `-PORT.` For example, 46 | if I had `ip172-18-0-22-bmf9t2ds883g009iqq80@direct.labs.play-with-docker.com` and wanted to look at port 3000, I would 47 | open ip172-18-0-22-bmf9t2ds883g009iqq80-3000.direct.labs.play-with-docker.com. 48 | 49 | -------------------------------------------------------------------------------- /docs_en/tutorial/image-building-best-practices/index.md: -------------------------------------------------------------------------------- 1 | 2 | ## Image Layering 3 | 4 | Did you know that you can look at what makes up an image? Using the `docker image history` 5 | command, you can see the command that was used to create each layer within an image. 6 | 7 | 1. Use the `docker image history` command to see the layers in the `docker-101` image you 8 | created earlier in the tutorial. 9 | 10 | ```bash 11 | docker image history docker-101 12 | ``` 13 | 14 | You should get output that looks something like this (dates/IDs may be different). 15 | 16 | ```plaintext 17 | IMAGE CREATED CREATED BY SIZE COMMENT 18 | a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "/app/src/ind… 0B 19 | f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB 20 | a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB 21 | 9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B 22 | b95baba1cfdb 7 weeks ago /bin/sh -c #(nop) CMD ["node"] 0B 23 | 7 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B 24 | 7 weeks ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B 25 | 7 weeks ago /bin/sh -c apk add --no-cache --virtual .bui… 5.5MB 26 | 7 weeks ago /bin/sh -c #(nop) ENV YARN_VERSION=1.17.3 0B 27 | 7 weeks ago /bin/sh -c addgroup -g 1000 node && addu… 65.4MB 28 | 7 weeks ago /bin/sh -c #(nop) ENV NODE_VERSION=10.16.3 0B 29 | 5 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B 30 | 5 months ago /bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6a… 5.53MB 31 | ``` 32 | 33 | Each of the lines represents a layer in the image. The display here shows the base at the bottom with 34 | the newest layer at the top. Using this, you can also quickly see the size of each layer, helping 35 | diagnose large images. 36 | 37 | 1. You'll notice that several of the lines are truncated. If you add the `--no-trunc` flag, you'll get the 38 | full output (yes... funny how you use a truncated flag to get untruncated output, huh?) 39 | 40 | ```bash 41 | docker image history --no-trunc docker-101 42 | ``` 43 | 44 | 45 | ## Layer Caching 46 | 47 | Now that you've seen the layering in action, there's an important lesson to learn to help decrease build 48 | times for your container images. 49 | 50 | > Once a layer changes, all downstream layers have to be recreated as well 51 | 52 | Let's look at the Dockerfile we were using one more time... 53 | 54 | ```dockerfile 55 | FROM node:10-alpine 56 | WORKDIR /app 57 | COPY . . 58 | RUN yarn install --production 59 | CMD ["node", "/app/src/index.js"] 60 | ``` 61 | 62 | Going back to the image history output, we see that each command in the Dockerfile becomes a new layer in the image. 63 | You might remember that when we made a change to the image, the yarn dependencies had to be reinstalled. Is there a 64 | way to fix this? It doesn't make much sense to ship around the same dependencies every time we build, right? 65 | 66 | To fix this, we need to restructure our Dockerfile to help support the caching of the dependencies. For Node-based 67 | applications, those dependencies are defined in the `package.json` file. So, what if we copied only that file in first, 68 | install the dependencies, and _then_ copy in everything else? Then, we only recreate the yarn dependencies if there was 69 | a change to the `package.json`. Make sense? 70 | 71 | 1. Update the Dockerfile to copy in the `package.json` first, install dependencies, and then copy everything else in. 72 | 73 | ```dockerfile hl_lines="3 4 5" 74 | FROM node:10-alpine 75 | WORKDIR /app 76 | COPY package.json yarn.lock ./ 77 | RUN yarn install --production 78 | COPY . . 79 | CMD ["node", "/app/src/index.js"] 80 | ``` 81 | 82 | 1. Build a new image using `docker build`. 83 | 84 | ```bash 85 | docker build -t docker-101 . 86 | ``` 87 | 88 | You should see output like this... 89 | 90 | ```plaintext 91 | Sending build context to Docker daemon 219.1kB 92 | Step 1/6 : FROM node:10-alpine 93 | ---> b95baba1cfdb 94 | Step 2/6 : WORKDIR /app 95 | ---> Using cache 96 | ---> 9577ae713121 97 | Step 3/6 : COPY package* yarn.lock ./ 98 | ---> bd5306f49fc8 99 | Step 4/6 : RUN yarn install --production 100 | ---> Running in d53a06c9e4c2 101 | yarn install v1.17.3 102 | [1/4] Resolving packages... 103 | [2/4] Fetching packages... 104 | info fsevents@1.2.9: The platform "linux" is incompatible with this module. 105 | info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. 106 | [3/4] Linking dependencies... 107 | [4/4] Building fresh packages... 108 | Done in 10.89s. 109 | Removing intermediate container d53a06c9e4c2 110 | ---> 4e68fbc2d704 111 | Step 5/6 : COPY . . 112 | ---> a239a11f68d8 113 | Step 6/6 : CMD ["node", "/app/src/index.js"] 114 | ---> Running in 49999f68df8f 115 | Removing intermediate container 49999f68df8f 116 | ---> e709c03bc597 117 | Successfully built e709c03bc597 118 | Successfully tagged docker-101:latest 119 | ``` 120 | 121 | You'll see that all layers were rebuilt. Perfectly fine since we changed the Dockerfile quite a bit. 122 | 123 | 1. Now, make a change to the `src/static/index.html` file (like change the `` to say "The Awesome Todo App"). 124 | 125 | 1. Build the Docker image now using `docker build` again. This time, your output should look a little different. 126 | 127 | ```plaintext hl_lines="5 8 11" 128 | Sending build context to Docker daemon 219.1kB 129 | Step 1/6 : FROM node:10-alpine 130 | ---> b95baba1cfdb 131 | Step 2/6 : WORKDIR /app 132 | ---> Using cache 133 | ---> 9577ae713121 134 | Step 3/6 : COPY package* yarn.lock ./ 135 | ---> Using cache 136 | ---> bd5306f49fc8 137 | Step 4/6 : RUN yarn install --production 138 | ---> Using cache 139 | ---> 4e68fbc2d704 140 | Step 5/6 : COPY . . 141 | ---> cccde25a3d9a 142 | Step 6/6 : CMD ["node", "/app/src/index.js"] 143 | ---> Running in 2be75662c150 144 | Removing intermediate container 2be75662c150 145 | ---> 458e5c6f080c 146 | Successfully built 458e5c6f080c 147 | Successfully tagged docker-101:latest 148 | ``` 149 | 150 | First off, you should notice that the build was MUCH faster! And, you'll see that steps 1-4 all have 151 | `Using cache`. So, hooray! We're using the build cache. Pushing and pulling this image and updates to it 152 | will be much faster as well. Hooray! 153 | 154 | 155 | ## Multi-Stage Builds 156 | 157 | While we're not going to dive into it too much in this tutorial, multi-stage builds are an incredibly powerful 158 | tool to help use multiple stages to create an image. There are several advantages for them: 159 | 160 | - Separate build-time dependencies from runtime dependencies 161 | - Reduce overall image size by shipping _only_ what your app needs to run 162 | 163 | ### Maven/Tomcat Example 164 | 165 | When building Java-based applications, a JDK is needed to compile the source code to Java bytecode. However, 166 | that JDK isn't needed in production. Also, you might be using tools like Maven or Gradle to help build the app. 167 | Those also aren't needed in our final image. Multi-stage builds help. 168 | 169 | ```dockerfile 170 | FROM maven AS build 171 | WORKDIR /app 172 | COPY . . 173 | RUN mvn package 174 | 175 | FROM tomcat 176 | COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 177 | ``` 178 | 179 | In this example, we use one stage (called `build`) to perform the actual Java build using Maven. In the second 180 | stage (starting at `FROM tomcat`), we copy in files from the `build` stage. The final image is only the last stage 181 | being created (which can be overridden using the `--target` flag). 182 | 183 | 184 | ### React Example 185 | 186 | When building React applications, we need a Node environment to compile the JS code (typically JSX), SASS stylesheets, 187 | and more into static HTML, JS, and CSS. If we aren't doing server-side rendering, we don't even need a Node environment 188 | for our production build. Why not ship the static resources in a static nginx container? 189 | 190 | ```dockerfile 191 | FROM node:10 AS build 192 | WORKDIR /app 193 | COPY package* yarn.lock ./ 194 | RUN yarn install 195 | COPY public ./public 196 | COPY src ./src 197 | RUN yarn run build 198 | 199 | FROM nginx:alpine 200 | COPY --from=build /app/build /usr/share/nginx/html 201 | ``` 202 | 203 | Here, we are using a `node:10` image to perform the build (maximizing layer caching) and then copying the output 204 | into an nginx container. Cool, huh? 205 | 206 | 207 | ## Recap 208 | 209 | By understanding a little bit about how images are structured, we can build images faster and ship fewer changes. 210 | Multi-stage builds also help us reduce overall image size and increase final container security by separating 211 | build-time dependencies from runtime dependencies. 212 | 213 | -------------------------------------------------------------------------------- /docs_en/tutorial/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | next_page: app.md 3 | --- 4 | 5 | ## The command you just ran 6 | 7 | Congratulations! You have started the container for this tutorial! 8 | Let's first explain the command that you just ran. In case you forgot, 9 | here's the command: 10 | 11 | ```cli 12 | docker run -d -p 80:80 dockersamples/101-tutorial 13 | ``` 14 | 15 | You'll notice a few flags being used. Here's some more info on them: 16 | 17 | - `-d` - run the container in detached mode (in the background) 18 | - `-p 80:80` - map port 80 of the host to port 80 in the container 19 | - `dockersamples/101-tutorial` - the image to use 20 | 21 | !!! info "Pro tip" 22 | You can combine single character flags to shorten the full command. 23 | As an example, the command above could be written as: 24 | ``` 25 | docker run -dp 80:80 dockersamples/101-tutorial 26 | ``` 27 | 28 | ## What is a container? 29 | 30 | Now that you've run a container, what _is_ a container? Simply put, a container is 31 | simply another process on your machine that has been isolated from all other processes 32 | 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 33 | in Linux for a long time. Docker has worked to make these capabilities approachable and easy to use. 34 | 35 | !!! info "Creating Containers from Scratch" 36 | If you'd like to see how containers are built from scratch, Liz Rice from Aqua Security 37 | has a fantastic talk in which she creates a container from scratch in Go. While she makes 38 | a simple container, this talk doesn't go into networking, using images for the filesystem, 39 | and more. But, it gives a _fantastic_ deep dive into how things are working. 40 | 41 | <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> 42 | 43 | ## What is a container image? 44 | 45 | When running a container, it uses an isolated filesystem. This custom filesystem is provided 46 | by a **container image**. Since the image contains the container's filesystem, it must contain everything 47 | needed to run an application - all dependencies, configuration, scripts, binaries, etc. The 48 | image also contains other configuration for the container, such as environment variables, 49 | a default command to run, and other metadata. 50 | 51 | We'll dive deeper into images later on, covering topics such as layering, best practices, and more. 52 | 53 | !!! info 54 | If you're familiar with `chroot`, think of a container as extended version of `chroot`. The 55 | filesystem is simply coming from the image. But, a container adds additional isolation not 56 | available when simply using chroot. 57 | 58 | -------------------------------------------------------------------------------- /docs_en/tutorial/multi-container-apps/multi-app-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/tutorial/multi-container-apps/multi-app-architecture.png -------------------------------------------------------------------------------- /docs_en/tutorial/our-application/index.md: -------------------------------------------------------------------------------- 1 | 2 | For the rest of this tutorial, we will be working with a simple todo 3 | list manager that is running in Node. If you're not familiar with Node, 4 | don't worry! No real JavaScript experience is needed! 5 | 6 | At this point, your development team is quite small and you're simply 7 | building an app to prove out your MVP (minimum viable product). You want 8 | to show how it works and what it's capable of doing without needing to 9 | think about how it will work for a large team, multiple developers, etc. 10 | 11 | ![Todo List Manager Screenshot](todo-list-sample.png){: style="width:50%;" } 12 | { .text-center } 13 | 14 | 15 | ## Getting our App into PWD 16 | 17 | Before we can run the application, we need to get the application source code into 18 | the Play with Docker environment. For real projects, you can clone the repo. But, in 19 | this case, you will upload a ZIP archive. 20 | 21 | 1. [Download the zip](/assets/app.zip) and upload it to Play with Docker. As a 22 | tip, you can drag and drop the zip (or any other file) on to the terminal in PWD. 23 | 24 | 1. In the PWD terminal, extract the zip file. 25 | 26 | ```bash 27 | unzip app.zip 28 | ``` 29 | 30 | 1. Change your current working directory into the new 'app' folder. 31 | 32 | ```bash 33 | cd app/ 34 | ``` 35 | 36 | 1. In this directory, you should see a simple Node-based application. 37 | 38 | ```bash 39 | ls 40 | package.json spec src yarn.lock 41 | ``` 42 | 43 | 44 | ## Building the App's Container Image 45 | 46 | In order to build the application, we need to use a `Dockerfile`. A 47 | Dockerfile is simply a text-based script of instructions that is used to 48 | create a container image. If you've created Dockerfiles before, you might 49 | see a few flaws in the Dockerfile below. But, don't worry! We'll go over them. 50 | 51 | 1. Create a file named Dockerfile with the following contents. 52 | 53 | ```dockerfile 54 | FROM node:10-alpine 55 | WORKDIR /app 56 | COPY . . 57 | RUN yarn install --production 58 | CMD ["node", "/app/src/index.js"] 59 | ``` 60 | 61 | 1. Build the container image using the `docker build` command. 62 | 63 | ```bash 64 | docker build -t docker-101 . 65 | ``` 66 | 67 | This command used the Dockerfile to build a new container image. You might 68 | have noticed that a lot of "layers" were downloaded. This is because we instructed 69 | the builder that we wanted to start from the `node:10-alpine` image. But, since we 70 | didn't have that on our machine, that image needed to be downloaded. 71 | 72 | After that, we copied in our application and used `yarn` to install our application's 73 | dependencies. The `CMD` directive specifies the default command to run when starting 74 | a container from this image. 75 | 76 | 77 | ## Starting an App Container 78 | 79 | Now that we have an image, let's run the application! To do so, we will use the `docker run` 80 | command (remember that from earlier?). 81 | 82 | 1. Start your container using the `docker run` command: 83 | 84 | ```bash 85 | docker run -dp 3000:3000 docker-101 86 | ``` 87 | 88 | Remember the `-d` and `-p` flags? We're running the new container in "detached" mode (in the 89 | background) and creating a mapping between the host's port 3000 to the container's port 3000. 90 | 91 | 1. Open the application by clicking on the "3000" badge at the top of the PWD interface. Once open, 92 | you should have an empty todo list! 93 | 94 | ![Empty Todo List](todo-list-empty.png){: style="width:450px;margin-top:20px;"} 95 | {: .text-center } 96 | 97 | 1. Go ahead and add an item or two and see that it works as you expect. You can mark items as 98 | complete and remove items. 99 | 100 | 101 | At this point, you should have a running todo list manager with a few items, all built by you! 102 | Now, let's make a few changes and learn about managing our containers. 103 | 104 | 105 | ## Recap 106 | 107 | In this short section, we learned the very basics about building a container image and created a 108 | Dockerfile to do so. Once we built an image, we started the container and saw the running app! 109 | 110 | Next, we're going to make a modification to our app and learn how to update our running application 111 | with a new image. Along the way, we'll learn a few other useful commands. 112 | -------------------------------------------------------------------------------- /docs_en/tutorial/our-application/todo-list-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/tutorial/our-application/todo-list-empty.png -------------------------------------------------------------------------------- /docs_en/tutorial/our-application/todo-list-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/tutorial/our-application/todo-list-sample.png -------------------------------------------------------------------------------- /docs_en/tutorial/persisting-our-data/index.md: -------------------------------------------------------------------------------- 1 | 2 | In case you didn't notice, our todo list is being wiped clean every single time 3 | we launch the container. Why is this? Let's dive into how the container is working. 4 | 5 | ## The Container's Filesystem 6 | 7 | When a container runs, it uses the various layers from an image for its filesystem. 8 | Each container also gets its own "scratch space" to create/update/remove files. Any 9 | changes won't be seen in another container, _even if_ they are using the same image. 10 | 11 | 12 | ### Seeing this in Practice 13 | 14 | To see this in action, we're going to start two containers and create a file in each. 15 | What you'll see is that the files created in one container aren't available in another. 16 | 17 | 1. Start a `ubuntu` container that will create a file named `/data.txt` with a random number 18 | between 1 and 10000. 19 | 20 | ```bash 21 | docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null" 22 | ``` 23 | 24 | In case you're wondering about the command, we're starting a bash shell and invoking two 25 | commands (why we have the `&&`). The first portion picks a single random number and writes 26 | it to `/data.txt`. The second command is simply watching a file to keep the container running. 27 | 28 | 1. Validate we can see the output by `exec`'ing into the container. To do so, you need to get the 29 | container's ID (use `docker ps` to get it). 30 | 31 | ```bash 32 | docker exec <container-id> cat /data.txt 33 | ``` 34 | 35 | You should see a random number! 36 | 37 | 1. Now, let's start another `ubuntu` container (the same image) and we'll see we don't have the same 38 | file. 39 | 40 | ```bash 41 | docker run -it ubuntu ls / 42 | ``` 43 | 44 | And look! There's no `data.txt` file there! That's because it was written to the scratch space for 45 | only the first container. 46 | 47 | 1. Go ahead and remove the first container using the `docker rm -f` command. 48 | 49 | 50 | ## Container Volumes 51 | 52 | With the previous experiment, we saw that each container restarts from scratch from its image. While containers 53 | can create, update, and delete files, those changes are lost when the container is removed and are isolated 54 | to that container. With volumes, we can change all of this. 55 | 56 | [Volumes](https://docs.docker.com/storage/volumes/) provide the ability to connect specific filesystem paths of 57 | the container back to the host machine. If a directory in the container is mounted, changes in that 58 | directory are also seen on the host machine. If we mount that same directory across container restarts, we'd see 59 | the same files. 60 | 61 | There are two main types of volumes. We will eventually use both, but we will start with **named volumes**. 62 | 63 | 64 | 65 | ## Persisting our Todo Data 66 | 67 | By default, the todo app stores its data in a [SQLite Database](https://www.sqlite.org/index.html) at 68 | `/etc/todos/todo.db`. If you're not familiar with SQLite, no worries! It's simply a relational database in 69 | which all of the data is stored in a single file. While this isn't the best for large-scale applications, 70 | it works for small demos. We'll talk about switching this to an actual database engine later. 71 | 72 | With the database being a single file, if we can persist that file on the host and make it available to the 73 | next container, it should be able to pick up where the last one left off. By creating a volume and attaching 74 | (often called "mounting") it to the directory the data is stored in, we can persist the data. As our container 75 | writes to the `todo.db` file, it will be persisted to the host in the volume. 76 | 77 | As mentioned, we are going to use a **named volume**. Think of a named volume as simply a bucket of data. 78 | Docker maintains the physical location on the disk and you only need to remember the name of the volume. 79 | Every time you use the volume, Docker will make sure the correct data is provided. 80 | 81 | 1. Create a volume by using the `docker volume create` command. 82 | 83 | ```bash 84 | docker volume create todo-db 85 | ``` 86 | 87 | 1. Start the todo container, but add the `-v` flag to specify a volume mount. We will use the named volume and mount 88 | it to `/etc/todos`, which will capture all files created at the path. 89 | 90 | ```bash 91 | docker run -dp 3000:3000 -v todo-db:/etc/todos docker-101 92 | ``` 93 | 94 | 1. Once the container starts up, open the app and add a few items to your todo list. 95 | 96 | ![Items added to todo list](items-added.png){: style="width: 55%; " } 97 | {: .text-center } 98 | 99 | 1. Remove the container for the todo app. Use `docker ps` to get the ID and then `docker rm -f <id>` to remove it. 100 | 101 | 1. Start a new container using the same command from above. 102 | 103 | 1. Open the app. You should see your items still in your list! 104 | 105 | 1. Go ahead and remove the container when you're done checking out your list. 106 | 107 | Hooray! You've now learned how to persist data! 108 | 109 | !!! info "Pro-tip" 110 | While named volumes and bind mounts (which we'll talk about in a minute) are the two main types of volumes supported 111 | by a default Docker engine installation, there are many volume driver plugins available to support NFS, SFTP, NetApp, 112 | and more! This will be especially important once you start running containers on multiple hosts in a clustered 113 | environment with Swarm, Kubernetes, etc. 114 | 115 | 116 | ## Diving into our Volume 117 | 118 | A lot of people frequently ask "Where is Docker _actually_ storing my data when I use a named volume?" If you want to know, 119 | you can use the `docker volume inspect` command. 120 | 121 | ```bash 122 | docker volume inspect todo-db 123 | [ 124 | { 125 | "CreatedAt": "2019-09-26T02:18:36Z", 126 | "Driver": "local", 127 | "Labels": {}, 128 | "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", 129 | "Name": "todo-db", 130 | "Options": {}, 131 | "Scope": "local" 132 | } 133 | ] 134 | ``` 135 | 136 | The `Mountpoint` is the actual location on the disk where the data is stored. Note that on most machines, you will 137 | need to have root access to access this directory from the host. But, that's where it is! 138 | 139 | 140 | ## Recap 141 | 142 | At this point, we have a functioning application that can survive restarts! We can show it off to our investors and 143 | hope they can catch our vision! 144 | 145 | However, we saw earlier that rebuilding images for every change takes quite a bit of time. There's got to be a better 146 | way to make changes, right? With bind mounts (which we hinted at earlier), there is a better way! Let's take a look at 147 | that now! 148 | -------------------------------------------------------------------------------- /docs_en/tutorial/persisting-our-data/items-added.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/tutorial/persisting-our-data/items-added.png -------------------------------------------------------------------------------- /docs_en/tutorial/sharing-our-app/index.md: -------------------------------------------------------------------------------- 1 | 2 | Now that we've built an image, let's share it! To share Docker images, you have to use a Docker 3 | registry. The default registry is Docker Hub and is where all of the images we've used have come from. 4 | 5 | ## Create a Repo 6 | 7 | To push an image, we first need to create a repo on Docker Hub. 8 | 9 | 1. Go to [Docker Hub](https://hub.docker.com) and log in if you need to. 10 | 11 | 1. Click the **Create Repository** button. 12 | 13 | 1. For the repo name, use `101-todo-app`. Make sure the Visibility is `Public`. 14 | 15 | 1. Click the **Create** button! 16 | 17 | If you look on the right-side of the page, you'll see a section named **Docker commands**. This gives 18 | an example command that you will need to run to push to this repo. 19 | 20 | ![Docker command with push example](push-command.png){: style=width:75% } 21 | {: .text-center } 22 | 23 | 24 | ## Pushing our Image 25 | 26 | 1. Back in your PWD instance, try running the command. You should get an error that looks something 27 | like this: 28 | 29 | ```plaintext 30 | $ docker push dockersamples/101-todo-app 31 | The push refers to repository [docker.io/dockersamples/101-todo-app] 32 | An image does not exist locally with the tag: dockersamples/101-todo-app 33 | ``` 34 | 35 | Why did it fail? The push command was looking for an image named dockersamples/101-todo-app, but 36 | didn't find one. If you run `docker image ls`, you won't see one either. 37 | 38 | To fix this, we need to "tag" our image, which basically means give it another name. 39 | 40 | 1. Login to the Docker Hub using the command `docker login -u YOUR-USER-NAME`. 41 | 42 | 1. Use the `docker tag` command to give the `docker-101` image a new name. Be sure to swap out 43 | `YOUR-USER-NAME` with your Docker ID. 44 | 45 | ```bash 46 | docker tag docker-101 YOUR-USER-NAME/101-todo-app 47 | ``` 48 | 49 | 1. Now try your push command again. If you're copying the value from Docker Hub, you can drop the 50 | `tagname` portion, as we didn't add a tag to the image name. 51 | 52 | ```bash 53 | docker push YOUR-USER-NAME/101-todo-app 54 | ``` 55 | 56 | ## Running our Image on a New Instance 57 | 58 | Now that our image has been built and pushed into a registry, let's try running our app on a brand 59 | instance that has never seen this container! 60 | 61 | 1. Back in PWD, click on **Add New Instance** to create a new instance. 62 | 63 | 1. In the new instance, start your freshly pushed app. 64 | 65 | ```bash 66 | docker run -dp 3000:3000 YOUR-USER-NAME/101-todo-app 67 | ``` 68 | 69 | You should see the image get pulled down and eventually start up! 70 | 71 | 1. Click on the 3000 badge when it comes up and you should see the app with your modifications! Hooray! 72 | 73 | 74 | ## Recap 75 | 76 | In this section, we learned how to share our images by pushing them to a registry. We then went to a 77 | brand new instance and were able to run the freshly pushed image. This is quite common in CI pipelines, 78 | where the pipeline will create the image and push it to a registry and then the production environment 79 | can use the latest version of the image. 80 | 81 | Now that we have that figured out, let's circle back around to what we noticed at the end of the last 82 | section. As a reminder, we noticed that when we restarted the app, we lost all of our todo list items. 83 | That's obviously not a great user experience, so let's learn how we can persist the data across 84 | restarts! -------------------------------------------------------------------------------- /docs_en/tutorial/sharing-our-app/push-command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/tutorial/sharing-our-app/push-command.png -------------------------------------------------------------------------------- /docs_en/tutorial/updating-our-app/index.md: -------------------------------------------------------------------------------- 1 | 2 | As a small feature request, we've been asked by the product team to 3 | change the "empty text" when we don't have any todo list items. They 4 | would like to transition it to the following: 5 | 6 | > You have no todo items yet! Add one above! 7 | 8 | Pretty simple, right? Let's make the change. 9 | 10 | ## Updating our Source Code 11 | 12 | 1. In the `~/app/src/static/js/app.js` file, update line 56 to use the new empty text. ([Editing files in PWD tips here](/pwd-tips#editing-files)) 13 | 14 | ```diff 15 | - <p className="text-center">No items yet! Add one above!</p> 16 | + <p className="text-center">You have no todo items yet! Add one above!</p> 17 | ``` 18 | 19 | 1. Let's build our updated version of the image, using the same command we used before. 20 | 21 | ```bash 22 | docker build -t docker-101 . 23 | ``` 24 | 25 | 1. Let's start a new container using the updated code. 26 | 27 | ```bash 28 | docker run -dp 3000:3000 docker-101 29 | ``` 30 | 31 | **Uh oh!** You probably saw an error like this (the IDs will be different): 32 | 33 | ```bash 34 | docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell 35 | (bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated. 36 | ``` 37 | 38 | So, what happened? We aren't able to start the new container because our old container is still 39 | running. The reason this is a problem is because that container is using the host's port 3000 and 40 | only one process (containers included) can listen to a specific port. To fix this, we need to remove 41 | the old container. 42 | 43 | 44 | ## Replacing our Old Container 45 | 46 | To remove a container, it first needs to be stopped. Then, it can be removed. 47 | 48 | 1. Get the ID of the container by using the `docker ps` command. 49 | 50 | ```bash 51 | docker ps 52 | ``` 53 | 54 | 1. Use the `docker stop` command to stop the container. 55 | 56 | ```bash 57 | # Swap out <the-container-id> with the ID from docker ps 58 | docker stop <the-container-id> 59 | ``` 60 | 61 | 1. Once the container has stopped, you can remove it by using the `docker rm` command. 62 | 63 | ```bash 64 | docker rm <the-container-id> 65 | ``` 66 | 67 | 1. Now, start your updated app. 68 | 69 | ```bash 70 | docker run -dp 3000:3000 docker-101 71 | ``` 72 | 73 | 1. Open the app and you should see your updated help text! 74 | 75 | ![Updated application with updated empty text](todo-list-updated-empty-text.png){: style="width:55%" } 76 | {: .text-center } 77 | 78 | !!! info "Pro tip" 79 | You can stop and remove a container in a single command by adding the "force" flag 80 | to the `docker rm` command. For example: `docker rm -f <the-container-id>` 81 | 82 | 83 | ## Recap 84 | 85 | While we were able to build an update, there were two things you might have noticed: 86 | 87 | - All of the existing items in our todo list are gone! That's not a very good app! We'll talk about that 88 | shortly. 89 | - There were _a lot_ of steps involved for such a small change. In an upcoming section, we'll talk about 90 | how to see code updates without needing to rebuild and start a new container every time we make a change. 91 | 92 | Before talking about persistence, we'll quickly see how to share these images with others. 93 | -------------------------------------------------------------------------------- /docs_en/tutorial/updating-our-app/todo-list-updated-empty-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/tutorial/updating-our-app/todo-list-updated-empty-text.png -------------------------------------------------------------------------------- /docs_en/tutorial/using-bind-mounts/index.md: -------------------------------------------------------------------------------- 1 | 2 | In the previous chapter, we talked about and used a **named volume** to persist the data in our database. 3 | Named volumes are great if we simply want to store data, as we don't have to worry about _where_ the data 4 | is stored. 5 | 6 | With **bind mounts**, we control the exact mountpoint on the host. We can use this to persist data, but is often 7 | used to provide additional data into containers. When working on an application, we can use a bind mount to 8 | mount our source code into the container to let it see code changes, respond, and let us see the changes right 9 | away. 10 | 11 | For Node-based applications, [nodemon](https://npmjs.com/package/nodemon) is a great tool to watch for file 12 | changes and then restart the application. There are equivalent tools in most other languages and frameworks. 13 | 14 | ## Quick Volume Type Comparisons 15 | 16 | Bind mounts and named volumes are the two main types of volumes that come with the Docker engine. However, additional 17 | 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). 18 | 19 | | | Named Volumes | Bind Mounts | 20 | | - | ------------- | ----------- | 21 | | Host Location | Docker chooses | You control | 22 | | Mount Example (using `-v`) | my-volume:/usr/local/data | /path/to/data:/usr/local/data | 23 | | Populates new volume with container contents | Yes | No | 24 | | Supports Volume Drivers | Yes | No | 25 | 26 | 27 | ## Starting a Dev-Mode Container 28 | 29 | To run our container to support a development workflow, we will do the following: 30 | 31 | - Mount our source code into the container 32 | - Install all dependencies, including the "dev" dependencies 33 | - Start nodemon to watch for filesystem changes 34 | 35 | So, let's do it! 36 | 37 | 1. Make sure you don't have any previous `docker-101` containers running. 38 | 39 | 1. Run the following command. We'll explain what's going on afterwards: 40 | 41 | ```bash 42 | docker run -dp 3000:3000 \ 43 | -w /app -v $PWD:/app \ 44 | node:10-alpine \ 45 | sh -c "yarn install && yarn run dev" 46 | ``` 47 | 48 | - `-dp 3000:3000` - same as before. Run in detached (background) mode and create a port mapping 49 | - `-w /app` - sets the "working directory" or the current directory that the command will run from 50 | - `node:10-alpine` - the image to use. Note that this is the base image for our app from the Dockerfile 51 | - `sh -c "yarn install && yarn run dev"` - the command. We're starting a shell using `sh` (alpine doesn't have `bash`) and 52 | running `yarn install` to install _all_ dependencies and then running `yarn run dev`. If we look in the `package.json`, 53 | we'll see that the `dev` script is starting `nodemon`. 54 | 55 | 1. You can watch the logs using `docker logs -f <container-id>`. You'll know you're ready to go when you see this... 56 | 57 | ```bash 58 | docker logs -f <container-id> 59 | $ nodemon src/index.js 60 | [nodemon] 1.19.2 61 | [nodemon] to restart at any time, enter `rs` 62 | [nodemon] watching dir(s): *.* 63 | [nodemon] starting `node src/index.js` 64 | Using sqlite database at /etc/todos/todo.db 65 | Listening on port 3000 66 | ``` 67 | 68 | When you're done watching the logs, exit out by hitting `Ctrl`+`C`. 69 | 70 | 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 71 | "Add". This change will be on line 109. 72 | 73 | ```diff 74 | - {submitting ? 'Adding...' : 'Add Item'} 75 | + {submitting ? 'Adding...' : 'Add'} 76 | ``` 77 | 78 | 1. Simply refresh the page (or open it) and you should see the change reflected in the browser almost immediately. It might 79 | take a few seconds for the Node server to restart, so if you get an error, just try refreshing after a few seconds. 80 | 81 | ![Screenshot of updated label for Add button](updated-add-button.png){: style="width:75%;"} 82 | {: .text-center } 83 | 84 | 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 85 | using `docker build -t docker-101 .`. 86 | 87 | 88 | Using bind mounts is _very_ common for local development setups. The advantage is that the dev machine doesn't need to have 89 | all of the build tools and environments installed. With a single `docker run` command, the dev environment is pulled and ready 90 | 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 91 | of flags). 92 | 93 | ## Recap 94 | 95 | At this point, we can persist our database and respond rapidly to the needs and demands of our investors and founders. Hooray! 96 | But, guess what? We received great news! 97 | 98 | **Your project has been selected for future development!** 99 | 100 | In order to prepare for production, we need to migrate our database from working in SQLite to something that can scale a 101 | little better. For simplicity, we'll keep with a relational database and switch our application to use MySQL. But, how 102 | should we run MySQL? How do we allow the containers to talk to each other? We'll talk about that next! 103 | -------------------------------------------------------------------------------- /docs_en/tutorial/using-bind-mounts/updated-add-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_en/tutorial/using-bind-mounts/updated-add-button.png -------------------------------------------------------------------------------- /docs_en/tutorial/what-next/index.md: -------------------------------------------------------------------------------- 1 | 2 | Although we're done with our workshop, there's still a LOT more to learn about containers! 3 | We're not going to go deep-dive here, but here are a few other areas to look at next! 4 | 5 | ## Container Orchestration 6 | 7 | Running containers in production is tough. You don't want to log into a machine and simply run a 8 | `docker run` or `docker-compose up`. Why not? Well, what happens if the containers die? How do you 9 | scale across several machines? Container orchestration solves this problem. Tools like Kubernetes, 10 | Swarm, Nomad, and ECS all help solve this problem, all in slightly different ways. 11 | 12 | The general idea is that you have "managers" who receive **expected state**. This state might be 13 | "I want to run two instances of my web app and expose port 80." The managers then look at all of the 14 | machines in the cluster and delegate work to "worker" nodes. The managers watch for changes (such as 15 | a container quitting) and then work to make **actual state** reflect the expected state. 16 | 17 | 18 | ## Cloud Native Computing Foundation Projects 19 | 20 | The CNCF is a vendor-neutral home for various open-source projects, including Kubernetes, Prometheus, 21 | Envoy, Linkerd, NATS, and more! You can view the [graduated and incubated projects here](https://www.cncf.io/projects/) 22 | and the entire [CNCF Landscape here](https://landscape.cncf.io/). There are a LOT of projects to help 23 | solve problems around monitoring, logging, security, image registries, messaging, and more! 24 | 25 | So, if you're new to the container landscape and cloud-native application development, welcome! Please 26 | connect to the community, ask questions, and keep learning! We're excited to have you! 27 | -------------------------------------------------------------------------------- /docs_es/consejos-para-usar-pwd/editor-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/consejos-para-usar-pwd/editor-button.png -------------------------------------------------------------------------------- /docs_es/consejos-para-usar-pwd/editor-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/consejos-para-usar-pwd/editor-display.png -------------------------------------------------------------------------------- /docs_es/consejos-para-usar-pwd/index.md: -------------------------------------------------------------------------------- 1 | 2 | ## Creación de archivos 3 | 4 | Si usted es nuevo en una terminal Linux, no se preocupe! La forma más fácil de crear un archivo es usar el comando `touch`. 5 | 6 | ```bash 7 | touch Dockerfile 8 | ``` 9 | 10 | Si ejecuta `ls`, verá que el archivo ha sido creado. Una vez creado, puede utilizar los siguientes consejos de **Edición de archivos**. 11 | 12 | 13 | ## Edición de archivos 14 | 15 | En PWD, puede utilizar cualquier editor basado en CLI. Sin embargo, sabemos que mucha gente no se siente tan cómoda en el CLI. Puede hacer clic en el botón "Editor" para obtener una vista del administrador de archivos. 16 | 17 | ![Editor Button](editor-button.png){: style=width:50% } 18 | {:.text-center} 19 | 20 | Después de hacer clic en el botón del editor, se abrirá el editor de archivos. La selección de un archivo proporcionará un editor basado en la web. 21 | 22 | ![Editor Display](editor-display.png){: style=width:75% } 23 | {: .text-center } 24 | 25 | 26 | ## Abrir una aplicación cuando la insignia se ha ido 27 | 28 | Si ha iniciado un contenedor, pero no aparece la insignia de puerto, existe una forma de abrir la aplicación. 29 | 30 | 1. En primer lugar, valida que el contenedor se está ejecutando y que no ha fallado al arrancar. Ejecute `docker ps` y compruebe que hay una asignación de puertos en la sección `PORTS`. 31 | 32 | ```bash 33 | $ docker ps 34 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 35 | 976e7066d705 docker-101 "docker-entrypoint.s…" 2 seconds ago Up 1 second 0.0.0.0:3000->3000/tcp xenodochial_ritchie 36 | ``` 37 | 38 | Si el contenedor no se inició, revise los registros del contenedor y trate de averiguar qué podría estar pasando. 39 | 40 | 1. En PWD, busque la pantalla `SSH`. Debería tener una pantalla que se parezca a `ssh ip172....`. Copia todo DESPUÉS de la parte `ssh` (`ip172.....`). 41 | 42 | 1. Pegue eso en el navegador web, pero NO presione enter todavía. Encuentra el símbolo `@`. Sustitúyalo por `-PORT.` Por ejemplo, si yo tuviera `ip172-18-0-22-bmf9t2ds883g009iqq80@direct.labs.play-with-docker.com` y quería ver el puerto 3000, debería abrir <code>ip172-18-0-22-bmf9t2ds883g009iqq80<strong>-3000</strong>.direct.labs.play-with-docker.com</code>. 43 | 44 | -------------------------------------------------------------------------------- /docs_es/css/dark-mode.css: -------------------------------------------------------------------------------- 1 | @media (prefers-color-scheme: dark) { 2 | .md-main { 3 | color: rgba(255, 255, 255, 0.75) !important; 4 | background-color: #36393e !important; 5 | } 6 | 7 | article img { 8 | box-shadow: 0 0 1em #000; 9 | } 10 | 11 | .md-main h1 { 12 | color: rgba(255, 255, 255, 0.8) !important; 13 | } 14 | blockquote { 15 | color: rgba(255, 255, 255, 0.75) !important; 16 | } 17 | table { 18 | background-color: #616161 !important; 19 | } 20 | tbody { 21 | background-color: #484848 !important; 22 | } 23 | .md-sidebar__scrollwrap::-webkit-scrollbar-thumb { 24 | background-color: #e0e0e0 !important; 25 | } 26 | .md-nav { 27 | color: rgba(255, 255, 255, 0.8) !important; 28 | background-color: #36393e !important; 29 | } 30 | html .md-nav--primary .md-nav__title:before { 31 | color: #fafafa !important; 32 | } 33 | .md-nav__title { 34 | color: rgba(255, 255, 255, 0.9) !important; 35 | background-color: #36393e !important; 36 | } 37 | .md-nav--primary .md-nav__link:after { 38 | color: #fafafa !important; 39 | } 40 | .md-nav__list { 41 | color: rgba(255, 255, 255, 0.8) !important; 42 | background-color: #36393e !important; 43 | } 44 | .md-nav__item { 45 | color: rgba(255, 255, 255, 0.7) !important; 46 | background-color: #36393e !important; 47 | } 48 | .md-search__scrollwrap::-webkit-scrollbar-thumb { 49 | background-color: #e0e0e0 !important; 50 | } 51 | .md-search__scrollwrap { 52 | background-color: #44484e !important; 53 | } 54 | .md-search-result__article--document:before { 55 | color: #eee !important; 56 | } 57 | .md-search-result__list { 58 | color: #eee !important; 59 | background-color: #36393e !important; 60 | } 61 | .md-search-result__meta { 62 | background-color: #eee !important; 63 | } 64 | .md-search-result__teaser { 65 | color: #bdbdbd !important; 66 | } 67 | .md-typeset code { 68 | color: white !important; 69 | /* box-shadow: 0.29412em 0 0 hsla(0, 0%, 100%, 0.07), 70 | -0.29412em 0 0 hsla(0, 0%, 100%, 0.1);*/ 71 | } 72 | .md-typeset a code { 73 | color: #94acff !important; 74 | } 75 | .md-typeset a:hover code { 76 | text-decoration: underline; 77 | } 78 | .linenos { 79 | color: #f5f5f5 !important; 80 | background-color: #313131 !important; 81 | } 82 | .codehilite { 83 | background-color: #44484e !important; 84 | } 85 | .md-typeset .codehilite::-webkit-scrollbar { 86 | height: 1rem !important; 87 | } 88 | .codehilite pre { 89 | color: #fafafa !important; 90 | background-color: transparent !important; 91 | } 92 | .codehilite .hll { 93 | background-color: #272822 !important; 94 | } 95 | .codehilite .c { 96 | color: #8a8f98 !important; 97 | } 98 | .codehilite .err { 99 | color: #960050 !important; 100 | background-color: #1e0010 !important; 101 | } 102 | .codehilite .k { 103 | color: #66d9ef !important; 104 | } 105 | .codehilite .l { 106 | color: #ae81ff !important; 107 | } 108 | .codehilite .n { 109 | color: #f8f8f2 !important; 110 | } 111 | .codehilite .o { 112 | color: #f92672 !important; 113 | } 114 | .codehilite .p { 115 | color: #f8f8f2 !important; 116 | } 117 | .codehilite .cm { 118 | color: #8a8f98 !important; 119 | } 120 | .codehilite .cp { 121 | color: #8a8f98 !important; 122 | } 123 | .codehilite .c1 { 124 | color: #8a8f98 !important; 125 | } 126 | .codehilite .cs { 127 | color: #8a8f98 !important; 128 | } 129 | .codehilite .ge { 130 | font-style: italic !important; 131 | } 132 | .codehilite .gs { 133 | font-weight: bold !important; 134 | } 135 | .codehilite .kc { 136 | color: #66d9ef !important; 137 | } 138 | .codehilite .kd { 139 | color: #66d9ef !important; 140 | } 141 | .codehilite .kn { 142 | color: #f92672 !important; 143 | } 144 | .codehilite .kp { 145 | color: #66d9ef !important; 146 | } 147 | .codehilite .kr { 148 | color: #66d9ef !important; 149 | } 150 | .codehilite .kt { 151 | color: #66d9ef !important; 152 | } 153 | .codehilite .ld { 154 | color: #e6db74 !important; 155 | } 156 | .codehilite .m { 157 | color: #ae81ff !important; 158 | } 159 | .codehilite .s { 160 | color: #e6db74 !important; 161 | } 162 | .codehilite .na { 163 | color: #a6e22e !important; 164 | } 165 | .codehilite .nb { 166 | color: #f8f8f2 !important; 167 | } 168 | .codehilite .nc { 169 | color: #a6e22e !important; 170 | } 171 | .codehilite .no { 172 | color: #66d9ef !important; 173 | } 174 | .codehilite .nd { 175 | color: #a6e22e !important; 176 | } 177 | .codehilite .ni { 178 | color: #f8f8f2 !important; 179 | } 180 | .codehilite .ne { 181 | color: #a6e22e !important; 182 | } 183 | .codehilite .nf { 184 | color: #a6e22e !important; 185 | } 186 | .codehilite .nl { 187 | color: #f8f8f2 !important; 188 | } 189 | .codehilite .nn { 190 | color: #f8f8f2 !important; 191 | } 192 | .codehilite .nx { 193 | color: #a6e22e !important; 194 | } 195 | .codehilite .py { 196 | color: #f8f8f2 !important; 197 | } 198 | .codehilite .nt { 199 | color: #f92672 !important; 200 | } 201 | .codehilite .nv { 202 | color: #f8f8f2 !important; 203 | } 204 | .codehilite .ow { 205 | color: #f92672 !important; 206 | } 207 | .codehilite .w { 208 | color: #f8f8f2 !important; 209 | } 210 | .codehilite .mf { 211 | color: #ae81ff !important; 212 | } 213 | .codehilite .mh { 214 | color: #ae81ff !important; 215 | } 216 | .codehilite .mi { 217 | color: #ae81ff !important; 218 | } 219 | .codehilite .mo { 220 | color: #ae81ff !important; 221 | } 222 | .codehilite .sb { 223 | color: #e6db74 !important; 224 | } 225 | .codehilite .sc { 226 | color: #e6db74 !important; 227 | } 228 | .codehilite .sd { 229 | color: #e6db74 !important; 230 | } 231 | .codehilite .s2 { 232 | color: #e6db74 !important; 233 | } 234 | .codehilite .se { 235 | color: #ae81ff !important; 236 | } 237 | .codehilite .sh { 238 | color: #e6db74 !important; 239 | } 240 | .codehilite .si { 241 | color: #e6db74 !important; 242 | } 243 | .codehilite .sx { 244 | color: #e6db74 !important; 245 | } 246 | .codehilite .sr { 247 | color: #e6db74 !important; 248 | } 249 | .codehilite .s1 { 250 | color: #e6db74 !important; 251 | } 252 | .codehilite .ss { 253 | color: #e6db74 !important; 254 | } 255 | .codehilite .bp { 256 | color: #f8f8f2 !important; 257 | } 258 | .codehilite .vc { 259 | color: #f8f8f2 !important; 260 | } 261 | .codehilite .vg { 262 | color: #f8f8f2 !important; 263 | } 264 | .codehilite .vi { 265 | color: #f8f8f2 !important; 266 | } 267 | .codehilite .il { 268 | color: #ae81ff !important; 269 | } 270 | .codehilite .gu { 271 | color: #8a8f98 !important; 272 | } 273 | .codehilite .gd { 274 | color: #9c1042 !important; 275 | background-color: #eaa; 276 | } 277 | .codehilite .gi { 278 | color: #364c0a !important; 279 | background-color: #91e891; 280 | } 281 | .md-clipboard:before { 282 | color: rgba(255, 255, 255, 0.31); 283 | } 284 | .codehilite:hover .md-clipboard:before, .md-typeset .highlight:hover .md-clipboard:before, pre:hover .md-clipboard:before { 285 | color: rgba(255, 255, 255, 0.6); 286 | } 287 | .md-typeset summary:after { 288 | color: rgba(255, 255, 255, 0.26); 289 | } 290 | .md-typeset .admonition.example > .admonition-title, .md-typeset .admonition.example > summary, .md-typeset details.example > .admonition-title, .md-typeset details.example > summary { 291 | background-color: rgba(154, 109, 255, 0.21); 292 | } 293 | .md-nav__link[data-md-state='blur'] { 294 | color: #aec0ff; 295 | } 296 | .md-typeset .footnote { 297 | color: #888484 !important; 298 | } 299 | .md-typeset .footnote-ref:before { 300 | border-color: #888484 !important; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /docs_es/css/styles.css: -------------------------------------------------------------------------------- 1 | .text-center { 2 | text-align: center; 3 | } 4 | 5 | article img { 6 | border: 1px solid #eee; 7 | box-shadow: 0 0 1em #ccc; 8 | margin: 30px auto 10px; 9 | } 10 | 11 | article img.emojione { 12 | border: none; 13 | box-shadow: none; 14 | margin: 0; 15 | } 16 | 17 | .md-footer-nav { 18 | background-color: rgba(0,0,0,.57); 19 | } 20 | 21 | 22 | /* Docker Branding */ 23 | .md-header { 24 | background-color: #0d9cec !important; 25 | } 26 | 27 | @font-face{ 28 | font-family: "Geomanist"; 29 | src: url("../fonts/hinted-Geomanist-Book.ttf") 30 | } 31 | 32 | body { 33 | font-family: "Open Sans", sans-serif; 34 | font-size: 15px; 35 | font-weight: normal; 36 | font-style: normal; 37 | font-stretch: normal; 38 | line-height: 1.5; 39 | letter-spacing: normal; 40 | color: #577482; 41 | } 42 | 43 | h1, h2, h3, h4, .md-footer-nav__inner, .md-header-nav__title, footer.md-footer { 44 | font-family: Geomanist; 45 | } 46 | 47 | .md-header-nav__title { 48 | line-height: 2.9rem; 49 | } 50 | 51 | .md-header-nav__button img { 52 | width: 145px; 53 | } -------------------------------------------------------------------------------- /docs_es/fonts/hinted-Geomanist-Book.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/fonts/hinted-Geomanist-Book.ttf -------------------------------------------------------------------------------- /docs_es/images/docker-labs-logo.svg: -------------------------------------------------------------------------------- 1 | <svg width="145" height="26.000000000000004" xmlns="http://www.w3.org/2000/svg" class="dicon " preserveAspectRatio="xMidYMid meet"> 2 | <g> 3 | <title>background 4 | 5 | 6 | 7 | Layer 1 8 | 9 | 10 | 11 | 12 | 13 | Labs 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs_es/images/pwd-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/images/pwd-badge.png -------------------------------------------------------------------------------- /docs_es/index.md: -------------------------------------------------------------------------------- 1 | redirect: /tutorial/ 2 | 3 | -------------------------------------------------------------------------------- /docs_es/js/custom.js: -------------------------------------------------------------------------------- 1 | $(window).load(function() { 2 | $('body').raptorize({ 3 | 'enterOn' : 'konami-code' 4 | }); 5 | }); -------------------------------------------------------------------------------- /docs_es/js/jquery.raptorize.1.0.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Raptorize Plugin 1.0 3 | * www.ZURB.com/playground 4 | * Copyright 2010, ZURB 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | 10 | (function($) { 11 | 12 | $.fn.raptorize = function(options) { 13 | 14 | //Yo' defaults 15 | var defaults = { 16 | enterOn: 'click', //timer, konami-code, click 17 | delayTime: 5000 //time before raptor attacks on timer mode 18 | }; 19 | 20 | //Extend those options 21 | var options = $.extend(defaults, options); 22 | 23 | return this.each(function() { 24 | 25 | var _this = $(this); 26 | var audioSupported = false; 27 | //Stupid Browser Checking which should be in jQuery Support 28 | if ($.browser.mozilla && $.browser.version.substr(0, 5) >= "1.9.2" || $.browser.webkit) { 29 | audioSupported = true; 30 | } 31 | 32 | //Raptor Vars 33 | var raptorImageMarkup = '' 34 | var raptorAudioMarkup = ''; 35 | var locked = false; 36 | 37 | //Append Raptor and Style 38 | $('body').append(raptorImageMarkup); 39 | if(audioSupported) { $('body').append(raptorAudioMarkup); } 40 | var raptor = $('#elRaptor').css({ 41 | "position":"fixed", 42 | "bottom": "-700px", 43 | "right" : "0", 44 | "display" : "block" 45 | }) 46 | 47 | // Animating Code 48 | function init() { 49 | locked = true; 50 | 51 | //Sound Hilarity 52 | if(audioSupported) { 53 | function playSound() { 54 | document.getElementById('elRaptorShriek').play(); 55 | } 56 | playSound(); 57 | } 58 | 59 | // Movement Hilarity 60 | raptor.animate({ 61 | "bottom" : "0" 62 | }, function() { 63 | $(this).animate({ 64 | "bottom" : "-130px" 65 | }, 100, function() { 66 | var offset = (($(this).position().left)+400); 67 | $(this).delay(300).animate({ 68 | "right" : offset 69 | }, 2200, function() { 70 | raptor = $('#elRaptor').css({ 71 | "bottom": "-700px", 72 | "right" : "0" 73 | }) 74 | locked = false; 75 | }) 76 | }); 77 | }); 78 | } 79 | 80 | 81 | //Determine Entrance 82 | if(options.enterOn == 'timer') { 83 | setTimeout(init, options.delayTime); 84 | } else if(options.enterOn == 'click') { 85 | _this.bind('click', function(e) { 86 | e.preventDefault(); 87 | if(!locked) { 88 | init(); 89 | } 90 | }) 91 | } else if(options.enterOn == 'konami-code'){ 92 | var kkeys = [], konami = "38,38,40,40,37,39,37,39,66,65"; 93 | $(window).bind("keydown.raptorz", function(e){ 94 | kkeys.push( e.keyCode ); 95 | if ( kkeys.toString().indexOf( konami ) >= 0 ) { 96 | init(); 97 | kkeys = []; 98 | // $(window).unbind('keydown.raptorz'); 99 | } 100 | }, true); 101 | 102 | } 103 | 104 | });//each call 105 | }//orbit plugin call 106 | })(jQuery); 107 | 108 | -------------------------------------------------------------------------------- /docs_es/js/raptor-sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/js/raptor-sound.mp3 -------------------------------------------------------------------------------- /docs_es/js/raptor-sound.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/js/raptor-sound.ogg -------------------------------------------------------------------------------- /docs_es/js/raptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/js/raptor.png -------------------------------------------------------------------------------- /docs_es/tutorial/actualizando-nuestra-aplicacion/index.md: -------------------------------------------------------------------------------- 1 | 2 | Como una pequeña petición de funcionalidad, el equipo de producto nos ha pedido que cambiemos el "texto vacío" cuando no tenemos ningún elemento de la lista de tareas pendientes. Les gustaría hacer la transición a lo siguiente: 3 | 4 | > ¡Todavía no tienes tareas que realizar! ¡Agregue una tarea arriba! 5 | 6 | Bastante simple, ¿verdad? Hagamos el cambio. 7 | 8 | ## Actualizando nuestro Código Fuente 9 | 10 | 1. En el archivo `~/app/src/static/js/app.js`, actualice la línea 56 para usar el nuevo texto vacío. ([Edición de archivos en PWD, algunos consejos aquí](/pwd-tips#editing-files)) 11 | 12 | ```diff 13 | -

No items yet! Add one above!

14 | +

¡Todavía no tienes tareas que realizar! ¡Agregue una tarea arriba!

15 | ``` 16 | 17 | 1. Vamos a construir nuestra versión actualizada de la imagen, usando el mismo comando que usamos antes. 18 | 19 | ```bash 20 | docker build -t docker-101 . 21 | ``` 22 | 23 | 1. Iniciemos un nuevo contenedor usando el código actualizado. 24 | 25 | ```bash 26 | docker run -dp 3000:3000 docker-101 27 | ``` 28 | 29 | **Ops!** Probablemente has visto un error como este (los identificaciones serán diferentes): 30 | 31 | ```bash 32 | docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell 33 | (bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated. 34 | ``` 35 | 36 | Entonces, ¿qué pasó? No podemos iniciar el nuevo contenedor porque el viejo aún está en funcionamiento. La razón por la que esto es un problema es porque ese contenedor está usando el puerto 3000 del host y sólo un proceso (contenedores incluidos) puede escuchar a un puerto específico. Para arreglar esto, necesitamos eliminar el contenedor obsoleto. 37 | 38 | 39 | ## Reemplazando nuestro contenedor obsoleto 40 | 41 | Para eliminar un contenedor, primero hay que detenerlo. Luego, puede ser eliminado. 42 | 43 | 1. Obtenga el ID del contenedor usando el comando `docker ps`. 44 | 45 | ```bash 46 | docker ps 47 | ``` 48 | 49 | 1. Utilice el comando `docker stop` para detener el contenedor. 50 | 51 | ```bash 52 | # Intercambiar con el ID del docker ps 53 | docker stop 54 | ``` 55 | 56 | 1. Una vez que el contenedor se ha detenido, puede eliminarlo utilizando el comando `docker rm`. 57 | 58 | ```bash 59 | docker rm 60 | ``` 61 | 62 | 1. Ahora, inicia tu aplicación actualizada. 63 | 64 | ```bash 65 | docker run -dp 3000:3000 docker-101 66 | ``` 67 | 68 | 1. Abre la aplicación y verás tu texto de ayuda actualizado! 69 | 70 | ![Aplicación actualizada con texto vacío actualizado](todo-list-updated-empty-text.png){: style="width:55%" } 71 | {: .text-center } 72 | 73 | !!! info "Consejo" 74 | Puede detener y eliminar un contenedor con un solo comando añadiendo la bandera "force" al comando `docker rm`. Por ejemplo: `docker rm -f `. 75 | 76 | 77 | ## Recapitulación 78 | 79 | Aunque hemos podido crear una actualización, hay dos cosas que puede que hayas notado: 80 | 81 | - Todos los artículos existentes en nuestra lista de cosas por hacer han desaparecido! Esa no es una muy buena aplicación! Hablaremos de eso pronto. 82 | - Había _muchos_ pasos involucrados para un cambio tan pequeño. En una próxima sección, hablaremos sobre cómo ver las actualizaciones de código sin necesidad de reconstruir y comenzar un nuevo contenedor cada vez que hagamos un cambio. 83 | 84 | Antes de hablar de persistencia, veremos rápidamente cómo compartir estas imágenes con otros. 85 | -------------------------------------------------------------------------------- /docs_es/tutorial/actualizando-nuestra-aplicacion/todo-list-updated-empty-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/actualizando-nuestra-aplicacion/todo-list-updated-empty-text.png -------------------------------------------------------------------------------- /docs_es/tutorial/aplicaciones-multi-contenedor/multi-app-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/aplicaciones-multi-contenedor/multi-app-architecture.png -------------------------------------------------------------------------------- /docs_es/tutorial/compartiendo-nuestra-aplicacion/index.md: -------------------------------------------------------------------------------- 1 | 2 | Ahora que hemos construido una imagen, ¡compartámosla! Para compartir imágenes Docker, debe utilizar un registro Docker. El registro por defecto es Docker Hub y es de donde vienen todas las imágenes que hemos usado. 3 | 4 | ## Crear un Repo 5 | 6 | Para subir (push) una imagen, primero tenemos que crear un repositorio en Docker Hub. 7 | 8 | 1. Ir a [Docker Hub](https://hub.docker.com) e identifíquese si es necesario. 9 | 10 | 1. Haga clic en el botón **Crear Repositorio**. 11 | 12 | 1. Para el nombre del repositorio, use `101-todo-app`. Asegúrese de que la visibilidad sea "pública". 13 | 14 | 1. ¡Haga clic en el botón **Crear**! 15 | 16 | Si miras en el lado derecho de la página, verás una sección llamada **Docker commands**. Esto da un comando de ejemplo que necesitará ejecutar para subir (push) su imagen a este repositorio. 17 | 18 | ![Comando Docker con ejemplo push](push-command.png){: style=width:75% } 19 | {: .text-center } 20 | 21 | 22 | ## Subiendo nuestra imagen 23 | 24 | 1. De vuelta en su instancia de PWD, intente ejecutar el comando. Debería aparecer un error que se parezca a esto: 25 | 26 | ```plaintext 27 | $ docker push dockersamples/101-todo-app 28 | The push refers to repository [docker.io/dockersamples/101-todo-app] 29 | An image does not exist locally with the tag: dockersamples/101-todo-app 30 | ``` 31 | 32 | ¿Por qué falló? El comando push estaba buscando una imagen llamada dockersamples/101-todo-app, pero no la encontró. Si ejecuta `docker image ls`, tampoco verá uno. 33 | 34 | Para arreglar esto, necesitamos "etiquetar" nuestra imagen, lo que básicamente significa darle otro nombre. 35 | 36 | 1. Inicie su sesión en Docker Hub con el comando `docker login -u TU-NOMBRE-DE-USUARIO`. 37 | 38 | 1. Utilice el comando `docker tag` para dar un nuevo nombre a la imagen `docker-101`. Asegúrate de cambiar "TU-NOMBRE-DE-USUARIO" por tu ID de Docker. 39 | 40 | ```bash 41 | docker tag docker-101 TU-NOMBRE-DE-USUARIO/101-todo-app 42 | ``` 43 | 44 | 1. Ahora intente su comando para subir la imagen de nuevo. Si está copiando el valor de Docker Hub, puede omitir la parte de `tagname`, ya que no añadimos una etiqueta al nombre de la imagen. 45 | 46 | ```bash 47 | docker push TU-NOMBRE-DE-USUARIO/101-todo-app 48 | ``` 49 | 50 | ## Ejecutar nuestra imagen en una nueva instancia 51 | 52 | Ahora que nuestra imagen ha sido construida e introducida en un registro, ¡vamos a intentar ejecutar nuestra aplicación en una instancia que nunca ha visto este contenedor! 53 | 54 | 1. De vuelta en PWD, haga clic en **Añadir Nueva Instancia** para crear una nueva instancia. 55 | 56 | 1. En la nueva instancia, inicia tu nueva aplicación. 57 | 58 | ```bash 59 | docker run -dp 3000:3000 TU-NOMBRE-DE-USUARIO/101-todo-app 60 | ``` 61 | 62 | Deberías ver que la imagen es descargada y ¡eventualmente puesta en marcha! 63 | 64 | 1. Haz clic en la insignia 3000 cuando aparezca y deberías ver la aplicación con tus modificaciones ¡Hurra! 65 | 66 | 67 | ## Recapitulación 68 | 69 | En esta sección, aprendimos a compartir nuestras imágenes subiéndolas a un registro. Luego fuimos a una nueva instancia y pudimos ejecutar la nueva imagen. Esto es bastante común en los Pipelines de CI, donde el Pipeline creará la imagen y la subirá a un registro y entonces el entorno de producción podrá utilizar la última versión de la imagen. 70 | 71 | Ahora que tenemos eso resuelto, volvamos a lo que notamos al final de la última sección. Como recordatorio, notamos que cuando reiniciamos la aplicación, perdimos todos los elementos de nuestra lista de tareas pendientes. Obviamente, esa no es una gran experiencia de usuario, ¡así que aprendamos cómo podemos conservar los datos en los reinicios! -------------------------------------------------------------------------------- /docs_es/tutorial/compartiendo-nuestra-aplicacion/push-command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/compartiendo-nuestra-aplicacion/push-command.png -------------------------------------------------------------------------------- /docs_es/tutorial/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | next_page: app.md 3 | --- 4 | 5 | ## El comando que acaba de ejecutar 6 | 7 | Felicitaciones! Ha iniciado el contenedor para este tutorial! 8 | Primero vamos a explicar el comando que acabas de ejecutar. Por si lo olvidaste, aquí está el comando: 9 | 10 | ```cli 11 | docker run -d -p 80:80 dockersamples/101-tutorial 12 | ``` 13 | 14 | Notarás que se están usando algunas banderas. Aquí hay más información sobre ellos: 15 | 16 | - `-d` - ejecuta el contenedor en modo independiente (tarea de fondo o segundo plano). 17 | - `-p 80:80` - asigna el puerto 80 del host al puerto 80 del contenedor 18 | - `dockersamples/101-tutorial` - la imagen a utilizar 19 | 20 | !!! info "Consejo" 21 | Puede combinar banderas de un solo carácter para acortar el comando completo. 22 | Por ejemplo, el comando anterior podría escribirse como: 23 | ``` 24 | docker run -dp 80:80 dockersamples/101-tutorial 25 | ``` 26 | 27 | ## ¿Qué es un contenedor? 28 | 29 | Ahora que ha ejecutado un contenedor, ¿qué es un contenedor? En pocas palabras, un contenedor es simplemente otro proceso en su máquina que ha sido aislado de todos los demás procesos en la máquina anfitriona (máquina host). Ese aislamiento aprovecha [namespaces del kernel y cgroups](https://medium.com/@saschagrunert/demystifying-containers-part-i-kernel-space-2c53d6979504), características que han estado en Linux durante mucho tiempo. Docker ha trabajado para que estas capacidades sean accesibles y fáciles de usar. 30 | 31 | !!! info "Creación de contenedores a partir de Scratch" 32 | Si quieres ver cómo se construyen los contenedores desde cero, Liz Rice de Aqua Security tiene una fantástica charla en la que crea un contenedor desde cero en Go. Aunque ella hace un simple contenedor, esta charla no entra en pronfundidad sobre el manejo de red, construcción de imágenes para el sistema de archivos, y más. Pero, da una _fantástica_ inmersión profunda en cómo están funcionando las cosas. 33 | 34 | 35 | 36 | ## ¿Qué es una imagen de contenedor? 37 | 38 | Cuando se ejecuta un contenedor, utiliza un sistema de archivos aislado. Este sistema de archivos personalizado es proporcionado por una **imagen del contenedor**. Dado que la imagen contiene el sistema de archivos del contenedor, debe contener todo lo necesario para ejecutar una aplicación: todas las dependencias, configuración, scripts, binarios, etc. La imagen también contiene otra configuración para el contenedor, como variables de entorno, un comando predeterminado para ejecutar y otros metadatos. 39 | 40 | Más adelante nos adentraremos más en las imágenes, cubriendo temas como las capas, las mejores prácticas y mucho más. 41 | 42 | !!! info 43 | Si está familiarizado con `chroot`, piense en un contenedor como una versión extendida de `chroot`. El sistema de archivos simplemente viene de la imagen. Pero, un contenedor añade un aislamiento adicional que no está disponible cuando se usa simplemente chroot. 44 | 45 | -------------------------------------------------------------------------------- /docs_es/tutorial/nuestra-aplicacion/index.md: -------------------------------------------------------------------------------- 1 | Para el resto de este tutorial, trabajaremos con un simple gestor de listas de tareas (todo list) que se está ejecutando en Node. Si no estás familiarizado con Node, ¡no te preocupes! ¡No se necesita experiencia real con JavaScript! 2 | 3 | En este punto, su equipo de desarrollo es bastante pequeño y usted simplemente está construyendo una aplicación para probar su MVP (producto mínimo viable). Quieres mostrar cómo funciona y lo que es capaz de hacer sin necesidad de pensar en cómo funcionará para un equipo grande, múltiples desarrolladores, etc. 4 | 5 | ![Todo List Manager Screenshot](todo-list-sample.png){: style="width:50%;" } 6 | { .text-center } 7 | 8 | ## Introduciendo nuestra aplicación en PWD 9 | 10 | Antes de que podamos ejecutar la aplicación, necesitamos obtener el código fuente de la aplicación en el entorno Play with Docker. Para proyectos reales, puedes clonar el repositorio. Pero, en este caso, usted subirá un archivo ZIP. 11 | 12 | 1. [Descarga el zip](/assets/app.zip) y cárgalo a Play with Docker. Como consejo, puede arrastrar y soltar el zip (o cualquier otro archivo) sobre el terminal en PWD. 13 | 14 | 1. En el terminal PWD, extraiga el archivo zip. 15 | 16 | ```bash 17 | unzip app.zip 18 | ``` 19 | 20 | 1. Cambie su directorio de trabajo actual a la nueva carpeta 'app'. 21 | 22 | ```bash 23 | cd app/ 24 | ``` 25 | 26 | 1. En este directorio, debería ver una aplicación simple basada en Node. 27 | 28 | ```bash 29 | ls 30 | package.json spec src yarn.lock 31 | ``` 32 | 33 | ## Creación de la imagen del contenedor con la aplicación 34 | 35 | Para construir la aplicación, necesitamos usar un `Dockerfile`. Un Dockerfile es simplemente un script de instrucciones basado en texto que se utiliza para crear una imagen de contenedor. Si ya ha creado Dockerfiles anteriormente, es posible que aparezcan algunos defectos en el Dockerfile que se muestra a continuación. Pero, ¡no te preocupes! Los revisaremos. 36 | 37 | 1. Cree un archivo llamado Dockerfile con el siguiente contenido. 38 | 39 | ```dockerfile 40 | FROM node:10-alpine 41 | WORKDIR /app 42 | COPY . . 43 | RUN yarn install --production 44 | CMD ["node", "/app/src/index.js"] 45 | ``` 46 | 47 | 1. Construya la imagen del contenedor usando el comando `docker build`. 48 | 49 | ```bash 50 | docker build -t docker-101 . 51 | ``` 52 | 53 | Este comando usó el Dockerfile para construir una nueva imagen del contenedor. Puede que haya notado que se han descargado muchas "capas". Esto se debe a que instruimos al constructor que queríamos empezar desde la imagen `node:10-alpine`. Pero, como no teníamos eso en nuestra máquina, esa imagen necesitaba ser descargada. 54 | 55 | Después de eso, copiamos en nuestra aplicación y usamos `yarn` para instalar las dependencias de nuestra aplicación. La directiva `CMD` especifica el comando por defecto que se ejecutará al iniciar un contenedor desde esta imagen. 56 | 57 | ## Iniciando el contenedor de la aplicación 58 | 59 | Ahora que tenemos una imagen, vamos a ejecutar la aplicación! Para ello, usaremos el comando `docker run` (¿recuerdas lo de antes?). 60 | 61 | 1. Inicie su contenedor usando el comando `docker run`: 62 | 63 | ```bash 64 | docker run -dp 3000:3000 docker-101 65 | ``` 66 | 67 | ¿Recuerdas las banderas `-d` y `-p`? Estamos ejecutando el nuevo contenedor en modo "separado" (en segundo plano) y creando un mapeo entre el puerto 3000 del host y el puerto 3000 del contenedor. 68 | 69 | 1. Abra la aplicación haciendo clic en la insignia "3000" en la parte superior de la interfaz PWD. Una vez abierto, ¡debería tener una lista de cosas por hacer vacía! 70 | 71 | ![Empty Todo List](todo-list-empty.png){: style="width:450px;margin-top:20px;"} 72 | {: .text-center } 73 | 74 | 1. Adelante, agregue uno o dos elementos y vea que funciona como usted espera. Puede marcar las posiciones como completas y eliminarlas. 75 | 76 | En este punto, deberías tener un administrador de listas de cosas por hacer con unos cuantos elementos, ¡todos construidos por ti! Ahora, hagamos algunos cambios y aprendamos sobre el manejo de nuestros contenedores. 77 | 78 | ## Recapitulación 79 | 80 | En esta breve sección, aprendimos lo básico sobre la construcción de una imagen de contenedor y creamos un Dockerfile para hacerlo. Una vez que construimos una imagen, iniciamos el contenedor y ¡vimos la aplicación en ejecución! 81 | 82 | A continuación, vamos a hacer una modificación a nuestra aplicación y aprender a actualizar nuestra aplicación en ejecución con una nueva imagen. En el camino, aprenderemos algunos otros comandos útiles. 83 | -------------------------------------------------------------------------------- /docs_es/tutorial/nuestra-aplicacion/todo-list-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/nuestra-aplicacion/todo-list-empty.png -------------------------------------------------------------------------------- /docs_es/tutorial/nuestra-aplicacion/todo-list-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/nuestra-aplicacion/todo-list-sample.png -------------------------------------------------------------------------------- /docs_es/tutorial/persistiendo-nuestra-base-de-datos/index.md: -------------------------------------------------------------------------------- 1 | 2 | En caso de que no lo hayas notado, nuestra lista de cosas por hacer está siendo limpiada cada vez que lanzamos el contenedor. ¿Por qué sucede esto? Vamos a ver cómo funciona el contenedor. 3 | 4 | ## El sistema de archivos del contenedor 5 | 6 | Cuando un contenedor se ejecuta, utiliza las diferentes capas de una imagen para su sistema de archivos. Cada contenedor también tiene su propio "espacio" para crear/actualizar/quitar archivos. Cualquier cambio no se verá en otro contenedor, _incluso si_ están usando la misma imagen. 7 | 8 | ![Sistemas de archivos de un contenedor](sistemas-de-archivos-de-un-contenedor.png){: style="width: 70%;" } 9 | {: .text-center } 10 | 11 | 12 | ### Veamos esto en la práctica 13 | 14 | Para ver esto en acción, vamos a iniciar dos contenedores y crear un archivo en cada uno. Lo que verá es que los archivos creados en un contenedor no están disponibles en otro. 15 | 16 | 1. Inicie un contenedor `ubuntu` que creará un archivo llamado `/data.txt` con un número aleatorio entre 1 y 10000. 17 | 18 | ```bash 19 | docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null" 20 | ``` 21 | 22 | En caso de que se esté preguntando por el comando, estamos iniciando un shell bash e invocando dos comandos (por qué tenemos el `&&`). La primera parte escoge un único número aleatorio y lo escribe en `/data.txt`. El segundo comando es simplemente mirar un archivo para mantener el contenedor funcionando. 23 | 24 | 1. Para validar el resultado, podemos ver la salida por `exec` en el contenedor. Para ello, necesita obtener el ID del contenedor (utilice `docker ps` para obtenerlo). 25 | 26 | ```bash 27 | docker exec cat /data.txt 28 | ``` 29 | 30 | Deberías ver un número al azar 31 | 32 | 1. Ahora, vamos a empezar otro contenedor `ubuntu` (la misma imagen) y veremos que no tenemos el mismo archivo. 33 | 34 | ```bash 35 | docker run -it ubuntu ls / 36 | ``` 37 | 38 | ¡Y mira! No hay ningún archivo `data.txt` allí. Esto se debe a que sólo fue escrito en el espacio del primer contenedor. 39 | 40 | 1. Adelante, proceda a eliminar el primer contenedor usando el comando `docker rm -f `. 41 | 42 | 43 | ## Volúmenes de Contenedores 44 | 45 | Con el experimento anterior, vimos que cada contenedor es efectivamente de sólo lectura. Mientras que los contenedores pueden crear, actualizar y eliminar archivos, esos cambios se pierden cuando se elimina el contenedor. Con los volúmenes, podemos cambiar todo esto. 46 | 47 | [Volúmenes](https://docs.docker.com/storage/volumes/) proporcionan la capacidad de conectar rutas específicas del sistema de archivos del contenedor de nuevo a la máquina anfitriona. Si se monta un directorio en el contenedor, los cambios en ese directorio también se ven en el equipo host. Si montamos ese mismo directorio en los reinicios del contenedor, veremos los mismos archivos. 48 | 49 | Hay dos tipos principales de volúmenes. Eventualmente usaremos ambos, pero empezaremos con **volúmenes nombrados**. 50 | 51 | 52 | 53 | ## Persistiendo datos de nuestro Gestor de Tareas (todo app) 54 | 55 | Por defecto, la aplicación "todo app" almacena sus datos en un archivo de base de datos [SQLite](https://www.sqlite.org/index.html) en `/etc/todos/todo.db`. Si no estás familiarizado con SQLite, ¡no te preocupes! Es simplemente una base de datos relacional en la que todos los datos se almacenan en un único archivo. Aunque esto no es lo mejor para aplicaciones a gran escala, funciona para pequeñas demostraciones. Hablaremos de cambiar esto a un motor de base de datos real más tarde. 56 | 57 | Con la base de datos siendo un solo archivo, si podemos mantener ese archivo en el host y ponerlo a disposición del siguiente contenedor, debería ser capaz de retomarlo donde lo dejó el último. Creando un volumen y adjuntándolo (a menudo llamado "montaje") al directorio en el que se almacenan los datos, podemos persistir en los datos. Mientras nuestro contenedor escribe en el archivo `todo.db`, persistirá en el host del volumen. 58 | 59 | Como se mencionó anteriormente, vamos a usar un **volumen nombrado**. Piense en un volumen nombrado como un simple cubo de datos. Docker mantiene la ubicación física en el disco y sólo necesita recordar el nombre del volumen. Cada vez que utilice el volumen, Docker se asegurará de que se proporcionen los datos correctos. 60 | 61 | 1. Cree un volumen utilizando el comando `docker volume create`. 62 | 63 | ```bash 64 | docker volume create todo-db 65 | ``` 66 | 67 | 1. Inicie el contenedor `docker-101`, pero añada el indicador `-v` para especificar un montaje de volumen. Usaremos el volumen nombrado y lo montaremos en `/etc/todos`, que capturará todos los archivos creados en la ruta. 68 | 69 | ```bash 70 | docker run -dp 3000:3000 -v todo-db:/etc/todos docker-101 71 | ``` 72 | 73 | 1. Una vez que el contenedor se inicie, abra la aplicación y añada algunos elementos a su lista de tareas pendientes. 74 | 75 | ![Items added to todo list](items-added.png){: style="width: 55%; " } 76 | {: .text-center } 77 | 78 | 1. Proceda a eliminar el contenedor `docker-101`. Utilice `docker ps` para obtener el ID y luego `docker rm -f ` para eliminarlo. 79 | 80 | 1. Inicie un nuevo contenedor usando el mismo comando del paso 2. 81 | 82 | 1. Abra la aplicación. Usted debe ver sus tareas todavía en su lista 83 | 84 | 1. Adelante, proceda a eliminar el contenedor cuando haya terminado de revisar su lista. 85 | 86 | ¡Genial! ¡Ahora ha aprendido a persistir los datos de su aplicación! 87 | 88 | !!! info "Consejo" 89 | Aunque los volúmenes nombrados y los montajes de enlace (de los que hablaremos en un minuto) son los dos tipos principales de volúmenes soportados por una instalación de motor Docker predeterminada, hay muchos plugins de controladores de volumen disponibles para soportar NFS, SFTP, NetApp y mucho más. Esto será especialmente importante una vez que comience a ejecutar contenedores en múltiples hosts en un entorno agrupado con Swarm, Kubernetes, etc. 90 | 91 | 92 | ## Sumérjase en nuestro Volumen 93 | 94 | Mucha gente se pregunta con frecuencia "¿Dónde está Docker _actualmente_ almacenando mis datos cuando uso un volumen nombrado?" Si quiere saber, puede usar el comando `docker volume inspect`. 95 | 96 | ```bash 97 | docker volume inspect todo-db 98 | [ 99 | { 100 | "CreatedAt": "2019-09-28T21:37:36Z", 101 | "Driver": "local", 102 | "Labels": {}, 103 | "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", 104 | "Name": "todo-db", 105 | "Options": {}, 106 | "Scope": "local" 107 | } 108 | ] 109 | ``` 110 | 111 | El `Mountpoint` es la ubicación real en el disco donde se almacenan los datos. Tenga en cuenta que en la mayoría de los equipos, necesitará tener acceso de root para acceder a este directorio desde el host. Pero, ahí es donde está. 112 | 113 | 114 | ## Recapitulación 115 | 116 | En este punto, tenemos una aplicación en funcionamiento que puede sobrevivir a los reinicios. ¡Podemos mostrarlo a nuestros inversores y esperar que puedan captar nuestra visión! 117 | 118 | Sin embargo, vimos anteriormente que la reconstrucción de imágenes para cada cambio toma bastante tiempo. Tiene que haber una forma mejor de hacer cambios, ¿verdad? Con el uso de `bind mounts` (que ya hemos mencionado anteriormente), ¡hay una forma mejor de hacerlo! Echemos un vistazo a eso ahora! 119 | -------------------------------------------------------------------------------- /docs_es/tutorial/persistiendo-nuestra-base-de-datos/items-added.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/persistiendo-nuestra-base-de-datos/items-added.png -------------------------------------------------------------------------------- /docs_es/tutorial/persistiendo-nuestra-base-de-datos/sistemas-de-archivos-de-un-contenedor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/persistiendo-nuestra-base-de-datos/sistemas-de-archivos-de-un-contenedor.png -------------------------------------------------------------------------------- /docs_es/tutorial/que-sigue/index.md: -------------------------------------------------------------------------------- 1 | 2 | Aunque hemos terminado con nuestro taller, todavía hay mucho más que aprender acerca de los contenedores! No vamos a hacer una inmersión profunda aquí, pero aquí hay algunas otras áreas para ver a continuación! 3 | 4 | ## Orquestación de contenedores 5 | 6 | El funcionamiento de los contenedores en la producción es difícil. Usted no quiere iniciar sesión en una máquina y simplemente ejecutar un `docker run` o `docker-composition up`. ¿Por qué no? Bueno, ¿qué pasa si los contenedores mueren? ¿Cómo se puede escalar a través de varias máquinas? La orquestación de contenedores resuelve este problema. Herramientas como Kubernetes, Swarm, Nomad y ECS ayudan a resolver este problema, todas estas herramientas lo hacen un poco diferente. 7 | 8 | La idea general es que usted tiene "gestores" que reciben **estado esperado**. Este estado podría ser "Quiero ejecutar dos instancias de mi aplicación web y exponer el puerto 80." Los gestores entonces miran todas las máquinas en el cluster y delegan el trabajo a los nodos "trabajadores". Los gestores están atentos a los cambios (como la salida de un contenedor) y luego trabajan para hacer que el **estado real** refleje el estado esperado. 9 | 10 | 11 | ## Proyectos de la Cloud Native Computing Foundation 12 | 13 | La CNCF es un proveedor neutral para varios proyectos de código abierto, incluyendo Kubernetes, Prometheus, Envoy, Linkerd, NATS, y más! Puede ver los [proyectos graduados e incubados aquí](https://www.cncf.io/projects/) y todo [lo relacionado a CNCF](https://landscape.cncf.io/). Hay un montón de proyectos para ayudar a resolver problemas de monitoreo, registro, seguridad, registros de imágenes, mensajería y más! 14 | 15 | Por lo tanto, si es nuevo en el mundo de los contenedores y en el desarrollo de aplicaciones nativas de la nube, ¡bienvenido! Por favor, conéctese con la comunidad, haga preguntas y siga aprendiendo! Estamos emocionados de tenerte! 16 | -------------------------------------------------------------------------------- /docs_es/tutorial/usando-bind-mounts/index.md: -------------------------------------------------------------------------------- 1 | 2 | En el capítulo anterior, hablamos y usamos un **volúmenes nombrados** para mantener los datos en nuestra base de datos. Los volúmenes nombrados son excelentes si simplemente queremos almacenar datos, ya que no tenemos que preocuparnos por _donde_ se almacenan los datos. 3 | 4 | Con **bind mounts**, controlamos el punto de montaje exacto en el host. Podemos utilizar esto para persistir en los datos, pero a menudo se utiliza para proporcionar datos adicionales en contenedores. Cuando trabajamos en una aplicación, podemos usar bind mount para montar nuestro código fuente en el contenedor para que vea los cambios de código, responda y nos permita ver los cambios de inmediato. 5 | 6 | Para aplicaciones basadas en Node, [nodemon](https://npmjs.com/package/nodemon) es una gran herramienta para observar los cambios en los archivos y luego reiniciar la aplicación. Existen herramientas equivalentes en la mayoría de los demás lenguajes y frameworks. 7 | 8 | ## Comparaciones rápidas de tipos de volumen 9 | 10 | Bind mounts y volúmenes nombrados son los dos tipos principales de volúmenes que vienen con el motor Docker. Sin embargo, hay controladores de volumen adicionales disponibles para soportar otros casos de uso. ([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), y más). 11 | 12 | | | Volúmenes Nombrados | Bind Mounts | 13 | | - | ------------- | ----------- | 14 | | Ubicación del Anfitrión (Host) | Docker escoge | Usted controla | 15 | | Ejemplo de Mount (usando `-v`) | volumen:/usr/local/data | /path/to/data:/usr/local/data | 16 | | Llena el nuevo volumen con el contenido del contenedor | Si | No | 17 | | Soporta controladores de volumen | Si | No | 18 | 19 | 20 | ## Inicio de un contenedor Modo-Dev 21 | 22 | Para ejecutar nuestro contenedor para soportar un flujo de trabajo de desarrollo, haremos lo siguiente: 23 | 24 | - Montar nuestro código fuente en el contenedor 25 | - Instalar todas las dependencias, incluyendo las dependencias "dev". 26 | - Iniciar nodemon para observar los cambios en el sistema de archivos 27 | 28 | Así que, ¡hagámoslo! 29 | 30 | 1. Asegúrese de no tener ningún contenedor `docker-101` en funcionamiento. 31 | 32 | 1. Ejecute el siguiente comando. Le explicaremos lo que pasa después: 33 | 34 | ```bash 35 | docker run -dp 3000:3000 \ 36 | -w /app -v $PWD:/app \ 37 | node:10-alpine \ 38 | sh -c "yarn install && yarn run dev" 39 | ``` 40 | 41 | - `-dp 3000:3000` - igual que antes. Ejecutar en modo independiente (en segundo plano) y crear una asignación de puertos 42 | - `-w /app` - establece el "directorio de trabajo" o el directorio actual desde el que se ejecutará el comando 43 | - `node:10-alpine` - la imagen a utilizar. Tenga en cuenta que esta es la imagen base de nuestra aplicación desde el Dockerfile 44 | - `sh -c "yarn install && yarn run dev"` - el comando. Estamos iniciando una shell usando `sh` (alpine no tiene `bash`) y ejecutando `yarn install` para instalar _todas las_ dependencias y luego ejecutando `yarn run dev`. Si miramos en el `package.json`, veremos que el script `dev` está empezando `nodemon`. 45 | 46 | 1. Puede ver los registros usando `docker logs -f `. 47 | 48 | ```bash 49 | docker logs -f 50 | $ nodemon src/index.js 51 | [nodemon] 1.19.2 52 | [nodemon] to restart at any time, enter `rs` 53 | [nodemon] watching dir(s): *.* 54 | [nodemon] starting `node src/index.js` 55 | Using sqlite database at /etc/todos/todo.db 56 | Listening on port 3000 57 | ``` 58 | 59 | Cuando termine de ver los registros, salga pulsando `Ctrl`+`C`. 60 | 61 | 1. Ahora, hagamos un cambio en la aplicación. En el archivo `src/static/js/app.js`, cambiemos el botón "Add Item" para decir simplemente "Agregar". Este cambio será en la línea 109. 62 | 63 | ```diff 64 | - {submitting ? 'Adding...' : 'Add Item'} 65 | + {submitting ? 'Adding...' : 'Agregar'} 66 | ``` 67 | 68 | 1. Simplemente actualice la página (o ábrala) y verá el cambio reflejado en el navegador casi inmediatamente. El servidor de Node puede tardar unos segundos en reiniciarse, así que si obtiene un error, intente actualizarlo después de unos segundos. 69 | 70 | ![Screenshot of updated label for Add button](updated-add-button.png){: style="width:75%;"} 71 | {: .text-center } 72 | 73 | 1. Siéntase libre de hacer cualquier otro cambio que desee hacer. Cuando haya terminado, detenga el contenedor y cree su nueva imagen usando `docker build -t docker-101 .`. 74 | 75 | 76 | El uso de bind mounts es _muy_ común en las configuraciones de desarrollo local. La ventaja es que la máquina de desarrollo no necesita tener todas las herramientas de construcción y entornos instalados. Con un solo comando `docker run`, el entorno de desarrollo se levanta y está listo para funcionar. Hablaremos de Docker Compose en un futuro, ya que esto nos ayudará a simplificar nuestros comandos (ya estamos recibiendo muchas banderas). 77 | 78 | ## Recapitulación 79 | 80 | En este punto, podemos persistir en nuestra base de datos y responder rápidamente a las necesidades y demandas de nuestros inversores y fundadores. ¡Hurra! Pero, ¿adivina qué? Recibimos grandes noticias! 81 | 82 | **¡Su proyecto ha sido seleccionado para ser llevado a producción!** 83 | 84 | Para ir a producción, necesitamos migrar nuestra base de datos, pasando de trabajar en SQLite a algo que pueda escalar un poco mejor. Para simplificar, nos mantendremos con una base de datos relacional y cambiaremos nuestra aplicación para usar MySQL. Pero, ¿cómo debemos ejecutar MySQL? ¿Cómo permitimos que los contenedores se comuniquen entre sí? ¡Hablaremos de eso después! 85 | -------------------------------------------------------------------------------- /docs_es/tutorial/usando-bind-mounts/updated-add-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_es/tutorial/usando-bind-mounts/updated-add-button.png -------------------------------------------------------------------------------- /docs_pt-br/css/dark-mode.css: -------------------------------------------------------------------------------- 1 | @media (prefers-color-scheme: dark) { 2 | .md-main { 3 | color: rgba(255, 255, 255, 0.75) !important; 4 | background-color: #36393e !important; 5 | } 6 | 7 | article img { 8 | box-shadow: 0 0 1em #000; 9 | } 10 | 11 | .md-main h1 { 12 | color: rgba(255, 255, 255, 0.8) !important; 13 | } 14 | blockquote { 15 | color: rgba(255, 255, 255, 0.75) !important; 16 | } 17 | table { 18 | background-color: #616161 !important; 19 | } 20 | tbody { 21 | background-color: #484848 !important; 22 | } 23 | .md-sidebar__scrollwrap::-webkit-scrollbar-thumb { 24 | background-color: #e0e0e0 !important; 25 | } 26 | .md-nav { 27 | color: rgba(255, 255, 255, 0.8) !important; 28 | background-color: #36393e !important; 29 | } 30 | html .md-nav--primary .md-nav__title:before { 31 | color: #fafafa !important; 32 | } 33 | .md-nav__title { 34 | color: rgba(255, 255, 255, 0.9) !important; 35 | background-color: #36393e !important; 36 | } 37 | .md-nav--primary .md-nav__link:after { 38 | color: #fafafa !important; 39 | } 40 | .md-nav__list { 41 | color: rgba(255, 255, 255, 0.8) !important; 42 | background-color: #36393e !important; 43 | } 44 | .md-nav__item { 45 | color: rgba(255, 255, 255, 0.7) !important; 46 | background-color: #36393e !important; 47 | } 48 | .md-search__scrollwrap::-webkit-scrollbar-thumb { 49 | background-color: #e0e0e0 !important; 50 | } 51 | .md-search__scrollwrap { 52 | background-color: #44484e !important; 53 | } 54 | .md-search-result__article--document:before { 55 | color: #eee !important; 56 | } 57 | .md-search-result__list { 58 | color: #eee !important; 59 | background-color: #36393e !important; 60 | } 61 | .md-search-result__meta { 62 | background-color: #eee !important; 63 | } 64 | .md-search-result__teaser { 65 | color: #bdbdbd !important; 66 | } 67 | .md-typeset code { 68 | color: white !important; 69 | /* box-shadow: 0.29412em 0 0 hsla(0, 0%, 100%, 0.07), 70 | -0.29412em 0 0 hsla(0, 0%, 100%, 0.1);*/ 71 | } 72 | .md-typeset a code { 73 | color: #94acff !important; 74 | } 75 | .md-typeset a:hover code { 76 | text-decoration: underline; 77 | } 78 | .linenos { 79 | color: #f5f5f5 !important; 80 | background-color: #313131 !important; 81 | } 82 | .codehilite { 83 | background-color: #44484e !important; 84 | } 85 | .md-typeset .codehilite::-webkit-scrollbar { 86 | height: 1rem !important; 87 | } 88 | .codehilite pre { 89 | color: #fafafa !important; 90 | background-color: transparent !important; 91 | } 92 | .codehilite .hll { 93 | background-color: #272822 !important; 94 | } 95 | .codehilite .c { 96 | color: #8a8f98 !important; 97 | } 98 | .codehilite .err { 99 | color: #960050 !important; 100 | background-color: #1e0010 !important; 101 | } 102 | .codehilite .k { 103 | color: #66d9ef !important; 104 | } 105 | .codehilite .l { 106 | color: #ae81ff !important; 107 | } 108 | .codehilite .n { 109 | color: #f8f8f2 !important; 110 | } 111 | .codehilite .o { 112 | color: #f92672 !important; 113 | } 114 | .codehilite .p { 115 | color: #f8f8f2 !important; 116 | } 117 | .codehilite .cm { 118 | color: #8a8f98 !important; 119 | } 120 | .codehilite .cp { 121 | color: #8a8f98 !important; 122 | } 123 | .codehilite .c1 { 124 | color: #8a8f98 !important; 125 | } 126 | .codehilite .cs { 127 | color: #8a8f98 !important; 128 | } 129 | .codehilite .ge { 130 | font-style: italic !important; 131 | } 132 | .codehilite .gs { 133 | font-weight: bold !important; 134 | } 135 | .codehilite .kc { 136 | color: #66d9ef !important; 137 | } 138 | .codehilite .kd { 139 | color: #66d9ef !important; 140 | } 141 | .codehilite .kn { 142 | color: #f92672 !important; 143 | } 144 | .codehilite .kp { 145 | color: #66d9ef !important; 146 | } 147 | .codehilite .kr { 148 | color: #66d9ef !important; 149 | } 150 | .codehilite .kt { 151 | color: #66d9ef !important; 152 | } 153 | .codehilite .ld { 154 | color: #e6db74 !important; 155 | } 156 | .codehilite .m { 157 | color: #ae81ff !important; 158 | } 159 | .codehilite .s { 160 | color: #e6db74 !important; 161 | } 162 | .codehilite .na { 163 | color: #a6e22e !important; 164 | } 165 | .codehilite .nb { 166 | color: #f8f8f2 !important; 167 | } 168 | .codehilite .nc { 169 | color: #a6e22e !important; 170 | } 171 | .codehilite .no { 172 | color: #66d9ef !important; 173 | } 174 | .codehilite .nd { 175 | color: #a6e22e !important; 176 | } 177 | .codehilite .ni { 178 | color: #f8f8f2 !important; 179 | } 180 | .codehilite .ne { 181 | color: #a6e22e !important; 182 | } 183 | .codehilite .nf { 184 | color: #a6e22e !important; 185 | } 186 | .codehilite .nl { 187 | color: #f8f8f2 !important; 188 | } 189 | .codehilite .nn { 190 | color: #f8f8f2 !important; 191 | } 192 | .codehilite .nx { 193 | color: #a6e22e !important; 194 | } 195 | .codehilite .py { 196 | color: #f8f8f2 !important; 197 | } 198 | .codehilite .nt { 199 | color: #f92672 !important; 200 | } 201 | .codehilite .nv { 202 | color: #f8f8f2 !important; 203 | } 204 | .codehilite .ow { 205 | color: #f92672 !important; 206 | } 207 | .codehilite .w { 208 | color: #f8f8f2 !important; 209 | } 210 | .codehilite .mf { 211 | color: #ae81ff !important; 212 | } 213 | .codehilite .mh { 214 | color: #ae81ff !important; 215 | } 216 | .codehilite .mi { 217 | color: #ae81ff !important; 218 | } 219 | .codehilite .mo { 220 | color: #ae81ff !important; 221 | } 222 | .codehilite .sb { 223 | color: #e6db74 !important; 224 | } 225 | .codehilite .sc { 226 | color: #e6db74 !important; 227 | } 228 | .codehilite .sd { 229 | color: #e6db74 !important; 230 | } 231 | .codehilite .s2 { 232 | color: #e6db74 !important; 233 | } 234 | .codehilite .se { 235 | color: #ae81ff !important; 236 | } 237 | .codehilite .sh { 238 | color: #e6db74 !important; 239 | } 240 | .codehilite .si { 241 | color: #e6db74 !important; 242 | } 243 | .codehilite .sx { 244 | color: #e6db74 !important; 245 | } 246 | .codehilite .sr { 247 | color: #e6db74 !important; 248 | } 249 | .codehilite .s1 { 250 | color: #e6db74 !important; 251 | } 252 | .codehilite .ss { 253 | color: #e6db74 !important; 254 | } 255 | .codehilite .bp { 256 | color: #f8f8f2 !important; 257 | } 258 | .codehilite .vc { 259 | color: #f8f8f2 !important; 260 | } 261 | .codehilite .vg { 262 | color: #f8f8f2 !important; 263 | } 264 | .codehilite .vi { 265 | color: #f8f8f2 !important; 266 | } 267 | .codehilite .il { 268 | color: #ae81ff !important; 269 | } 270 | .codehilite .gu { 271 | color: #8a8f98 !important; 272 | } 273 | .codehilite .gd { 274 | color: #9c1042 !important; 275 | background-color: #eaa; 276 | } 277 | .codehilite .gi { 278 | color: #364c0a !important; 279 | background-color: #91e891; 280 | } 281 | .md-clipboard:before { 282 | color: rgba(255, 255, 255, 0.31); 283 | } 284 | .codehilite:hover .md-clipboard:before, .md-typeset .highlight:hover .md-clipboard:before, pre:hover .md-clipboard:before { 285 | color: rgba(255, 255, 255, 0.6); 286 | } 287 | .md-typeset summary:after { 288 | color: rgba(255, 255, 255, 0.26); 289 | } 290 | .md-typeset .admonition.example > .admonition-title, .md-typeset .admonition.example > summary, .md-typeset details.example > .admonition-title, .md-typeset details.example > summary { 291 | background-color: rgba(154, 109, 255, 0.21); 292 | } 293 | .md-nav__link[data-md-state='blur'] { 294 | color: #aec0ff; 295 | } 296 | .md-typeset .footnote { 297 | color: #888484 !important; 298 | } 299 | .md-typeset .footnote-ref:before { 300 | border-color: #888484 !important; 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /docs_pt-br/css/styles.css: -------------------------------------------------------------------------------- 1 | .text-center { 2 | text-align: center; 3 | } 4 | 5 | article img { 6 | border: 1px solid #eee; 7 | box-shadow: 0 0 1em #ccc; 8 | margin: 30px auto 10px; 9 | } 10 | 11 | article img.emojione { 12 | border: none; 13 | box-shadow: none; 14 | margin: 0; 15 | } 16 | 17 | .md-footer-nav { 18 | background-color: rgba(0,0,0,.57); 19 | } 20 | 21 | 22 | /* Docker Branding */ 23 | .md-header { 24 | background-color: #0d9cec !important; 25 | } 26 | 27 | @font-face{ 28 | font-family: "Geomanist"; 29 | src: url("../fonts/hinted-Geomanist-Book.ttf") 30 | } 31 | 32 | body { 33 | font-family: "Open Sans", sans-serif; 34 | font-size: 15px; 35 | font-weight: normal; 36 | font-style: normal; 37 | font-stretch: normal; 38 | line-height: 1.5; 39 | letter-spacing: normal; 40 | color: #577482; 41 | } 42 | 43 | h1, h2, h3, h4, .md-footer-nav__inner, .md-header-nav__title, footer.md-footer { 44 | font-family: Geomanist; 45 | } 46 | 47 | .md-header-nav__title { 48 | line-height: 2.9rem; 49 | } 50 | 51 | .md-header-nav__button img { 52 | width: 145px; 53 | } -------------------------------------------------------------------------------- /docs_pt-br/dicas-pwd/editor-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/dicas-pwd/editor-button.png -------------------------------------------------------------------------------- /docs_pt-br/dicas-pwd/editor-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/dicas-pwd/editor-display.png -------------------------------------------------------------------------------- /docs_pt-br/dicas-pwd/index.md: -------------------------------------------------------------------------------- 1 | 2 | ## Criando Arquivos 3 | 4 | Se você é novo no terminal Linux, não se preocupe! O jeito mais fácil de criar um arquivo é utilizando o 5 | comando `touch`. 6 | 7 | ```bash 8 | touch Dockerfile 9 | ``` 10 | 11 | Se você rodar `ls`, você verá que o arquivo foi criado. Uma vez criado, você pode utilizar as dicas **Editando Arquivos** abaixo. 12 | 13 | ## Editando Arquivos 14 | 15 | No PWD, você pode utilizar qualquer editor de texto baseado em CLI. Entretanto, sabemos que muitos não se sentem 16 | confortáveis utilizando o CLI. 17 | Você pode clicar no botão "Editor" para abrir a janela do gerenciador de arquivos. 18 | 19 | ![Editor Button](editor-button.png){: style=width:50% } 20 | {:.text-center} 21 | 22 | Depois de clicar no botão editor, o editor de arquivo irá abrir. Selecionando um arquivo fornecerá um editor 23 | baseado na web. 24 | 25 | ![Editor Display](editor-display.png){: style=width:75% } 26 | {: .text-center } 27 | 28 | ## Abrindo uma Aplicação quando não tiver o botão 29 | 30 | Se você iniciou um contêiner, mas o botão da porta não apareceu, ainda existe um jeito de abrir a aplicação. 31 | 32 | 1. Primeiro, valide se o contêiner está realmente rodando e apenas não falhou ao iniciar. Rode `docker ps` e 33 | verifique se a porta está mapeada na seção `PORTS`. 34 | 35 | ```bash 36 | $ docker ps 37 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 38 | 976e7066d705 docker-101 "docker-entrypoint.s…" 2 seconds ago Up 1 second 0.0.0.0:3000->3000/tcp xenodochial_ritchie 39 | ``` 40 | 41 | Se o contêiner falhou ao iniciar, verifique os logs do contêiner e tente descobrir o que aconteceu. 42 | 43 | 1. No PWD, procure o painel `SSH`. Ele deve mostrar algo parecido com `ssh ip172...`. Copie tudo DEPOIS da parte 44 | do `ssh` (`ip172....`). 45 | 46 | 1. Cole isso no navegador, mas NÃO aperte enter ainda. Procure o símbolo `@`. Substitua por `-PORT`. Por exemplo, 47 | se eu tivesse `ip172-18-0-22-bmf9t2ds883g009iqq80@direct.labs.play-with-docker.com` e quisesse ver a porta 3000, eu iria 48 | abrir ip172-18-0-22-bmf9t2ds883g009iqq80-3000.direct.labs.play-with-docker.com. -------------------------------------------------------------------------------- /docs_pt-br/fonts/hinted-Geomanist-Book.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/fonts/hinted-Geomanist-Book.ttf -------------------------------------------------------------------------------- /docs_pt-br/images/docker-labs-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | background 4 | 5 | 6 | 7 | Layer 1 8 | 9 | 10 | 11 | 12 | 13 | Labs 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs_pt-br/images/pwd-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/images/pwd-badge.png -------------------------------------------------------------------------------- /docs_pt-br/index.md: -------------------------------------------------------------------------------- 1 | redirect: /tutorial/ 2 | 3 | -------------------------------------------------------------------------------- /docs_pt-br/js/custom.js: -------------------------------------------------------------------------------- 1 | $(window).load(function() { 2 | $('body').raptorize({ 3 | 'enterOn' : 'konami-code' 4 | }); 5 | }); -------------------------------------------------------------------------------- /docs_pt-br/js/jquery.raptorize.1.0.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Raptorize Plugin 1.0 3 | * www.ZURB.com/playground 4 | * Copyright 2010, ZURB 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | */ 8 | 9 | 10 | (function($) { 11 | 12 | $.fn.raptorize = function(options) { 13 | 14 | //Yo' defaults 15 | var defaults = { 16 | enterOn: 'click', //timer, konami-code, click 17 | delayTime: 5000 //time before raptor attacks on timer mode 18 | }; 19 | 20 | //Extend those options 21 | var options = $.extend(defaults, options); 22 | 23 | return this.each(function() { 24 | 25 | var _this = $(this); 26 | var audioSupported = false; 27 | //Stupid Browser Checking which should be in jQuery Support 28 | if ($.browser.mozilla && $.browser.version.substr(0, 5) >= "1.9.2" || $.browser.webkit) { 29 | audioSupported = true; 30 | } 31 | 32 | //Raptor Vars 33 | var raptorImageMarkup = '' 34 | var raptorAudioMarkup = ''; 35 | var locked = false; 36 | 37 | //Append Raptor and Style 38 | $('body').append(raptorImageMarkup); 39 | if(audioSupported) { $('body').append(raptorAudioMarkup); } 40 | var raptor = $('#elRaptor').css({ 41 | "position":"fixed", 42 | "bottom": "-700px", 43 | "right" : "0", 44 | "display" : "block" 45 | }) 46 | 47 | // Animating Code 48 | function init() { 49 | locked = true; 50 | 51 | //Sound Hilarity 52 | if(audioSupported) { 53 | function playSound() { 54 | document.getElementById('elRaptorShriek').play(); 55 | } 56 | playSound(); 57 | } 58 | 59 | // Movement Hilarity 60 | raptor.animate({ 61 | "bottom" : "0" 62 | }, function() { 63 | $(this).animate({ 64 | "bottom" : "-130px" 65 | }, 100, function() { 66 | var offset = (($(this).position().left)+400); 67 | $(this).delay(300).animate({ 68 | "right" : offset 69 | }, 2200, function() { 70 | raptor = $('#elRaptor').css({ 71 | "bottom": "-700px", 72 | "right" : "0" 73 | }) 74 | locked = false; 75 | }) 76 | }); 77 | }); 78 | } 79 | 80 | 81 | //Determine Entrance 82 | if(options.enterOn == 'timer') { 83 | setTimeout(init, options.delayTime); 84 | } else if(options.enterOn == 'click') { 85 | _this.bind('click', function(e) { 86 | e.preventDefault(); 87 | if(!locked) { 88 | init(); 89 | } 90 | }) 91 | } else if(options.enterOn == 'konami-code'){ 92 | var kkeys = [], konami = "38,38,40,40,37,39,37,39,66,65"; 93 | $(window).bind("keydown.raptorz", function(e){ 94 | kkeys.push( e.keyCode ); 95 | if ( kkeys.toString().indexOf( konami ) >= 0 ) { 96 | init(); 97 | kkeys = []; 98 | // $(window).unbind('keydown.raptorz'); 99 | } 100 | }, true); 101 | 102 | } 103 | 104 | });//each call 105 | }//orbit plugin call 106 | })(jQuery); 107 | 108 | -------------------------------------------------------------------------------- /docs_pt-br/js/raptor-sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/js/raptor-sound.mp3 -------------------------------------------------------------------------------- /docs_pt-br/js/raptor-sound.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/js/raptor-sound.ogg -------------------------------------------------------------------------------- /docs_pt-br/js/raptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/js/raptor.png -------------------------------------------------------------------------------- /docs_pt-br/tutorial/aplicacoes-multi-conteiner/multi-app-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/tutorial/aplicacoes-multi-conteiner/multi-app-architecture.png -------------------------------------------------------------------------------- /docs_pt-br/tutorial/atualizando-nossa-aplicacao/index.md: -------------------------------------------------------------------------------- 1 | 2 | Como uma pequena solicitação de funcionalidade, fomos acionados pela equipe de produto para que seja alterado 3 | o "texto vazio" quando não tivermos nenhum item da lista de tarefas. Eles 4 | gostariam de fazer a transição para o seguinte: 5 | 6 | > Sem tarefas ainda! Adicione uma acima! 7 | 8 | Simples, não? Vamos fazer essa mudança. 9 | 10 | ## Atualizando nosso código fonte 11 | 12 | 1. No arquivo `~/app/src/static/js/app.js` atualize a linha 56 para que o novo "texto vazio" seja utilizado. ([Dicas de edição de arquivos no PWD aqui](/dicas-pwd/#editando-arquivos)) 13 | 14 | ```diff 15 | -

No items yet! Add one above!

16 | +

Sem tarefas ainda! Adicione uma acima!

17 | ``` 18 | 19 | 1. Vamos construir uma versão atualizada da imagem usando o mesmo comando usado anteriormente. 20 | 21 | ```bash 22 | docker build -t docker-101 . 23 | ``` 24 | 25 | 1. Vamos iniciar um novo contêiner usando a imagem com código atualizado. 26 | 27 | ```bash 28 | docker run -dp 3000:3000 docker-101 29 | ``` 30 | 31 | **Oh!** Você provavelmente viu um erro como o seguinte (os IDs serão diferentes): 32 | 33 | ```bash 34 | docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell 35 | (bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated. 36 | ``` 37 | 38 | Então o que aconteceu? Não podemos iniciar o novo contêiner porque nosso contêiner antigo ainda está 39 | em execução. A razão pela qual isso é um problema é porque esse contêiner está usando a porta 3000 e 40 | apenas um processo (contêineres inclusos) pode ouvir uma porta específica. Para corrigir isso, precisamos remover 41 | o contêiner antigo. 42 | 43 | 44 | ## Substituindo nosso contêiner velho 45 | 46 | Para remover um contêiner, ele primeiro precisa ser parado. Em seguida, pode ser removido. 47 | 48 | 1. Obtenha o ID do contêiner usando o comando `docker ps`. 49 | 50 | ```bash 51 | docker ps 52 | ``` 53 | 54 | 1. Use o comando `docker stop` para parar o contêiner. 55 | 56 | ```bash 57 | # Troque pelo ID obtido com o comando docker ps 58 | docker stop 59 | ``` 60 | 61 | 1. Depois que o contêiner parar, você pode removê-lo usando o comando `docker rm`. 62 | 63 | ```bash 64 | docker rm 65 | ``` 66 | 67 | 1. Agora, inicie sua aplicação atualizada. 68 | 69 | ```bash 70 | docker run -dp 3000:3000 docker-101 71 | ``` 72 | 73 | 1. Abra a aplicação e você verá seu texto de ajuda atualizado! 74 | 75 | ![Aplicação atualizada com texto vazio atualizado](todo-list-updated-empty-text.png){: style="width:55%" } 76 | {: .text-center } 77 | 78 | !!! info "Dica" 79 | Você pode parar e remover um contêiner em um único comando adicionando a opção "force" 80 | ao comando `docker rm`. Por exemplo: `docker rm -f ` 81 | 82 | ## Recapitulando 83 | 84 | Embora pudéssemos criar uma atualização, tem duas coisas que você deve ter notado: 85 | 86 | - Todos os itens existentes em nossa lista de tarefas desapareceram! Esse não é muito bom para uma aplicação! Falaremos sobre isso 87 | Em breve. 88 | - Havia muitas etapas envolvidas para uma mudança tão pequena. Em uma próxima seção, falaremos sobre 89 | como ver as atualizações de código sem precisar reconstruir e iniciar um novo contêiner toda vez que fazemos uma alteração. 90 | 91 | Antes de falar sobre a persistência, veremos rapidamente como compartilhar essas imagens com outras pessoas. 92 | -------------------------------------------------------------------------------- /docs_pt-br/tutorial/atualizando-nossa-aplicacao/todo-list-updated-empty-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/tutorial/atualizando-nossa-aplicacao/todo-list-updated-empty-text.png -------------------------------------------------------------------------------- /docs_pt-br/tutorial/compartilhando-nossa-aplicacao/index.md: -------------------------------------------------------------------------------- 1 | 2 | Agora que criamos uma imagem, vamos compartilhá-la! Para compartilhar imagens do Docker, você precisa usar o Docker Registry, 3 | o repositório padrão é o Docker Hub e é de onde vieram todas as imagens que usamos. 4 | 5 | ## Criando um repositório 6 | 7 | Para enviar uma imagem por push, primeiro precisamos criar um repositório no Docker Hub. 8 | 9 | 1. Vá até [Docker Hub](https://hub.docker.com) e faça login se precisar. 10 | 11 | 1. Clique no botão **Create Repository**. 12 | 13 | 1. Como nome de repositório, utilize `101-todo-app`. Verifique se a visibilidade está como `Public`. 14 | 15 | 1. Clique no botão **Create**! 16 | 17 | Se você olhar no lado direito da página, verá uma seção chamada **Docker commands**. Isto dá 18 | um exemplo de comando que você precisará executar para enviar para este repositório. 19 | 20 | ![Docker command with push example](push-command.png){: style=width:75% } 21 | {: .text-center } 22 | 23 | 24 | ## Enviando sua imagem 25 | 26 | 1. De volta à sua instância do PWD, tente executar o comando, você deve receber um erro que parecido 27 | como isso: 28 | 29 | ```plaintext 30 | $ docker push dockersamples/101-todo-app 31 | The push refers to repository [docker.io/dockersamples/101-todo-app] 32 | An image does not exist locally with the tag: dockersamples/101-todo-app 33 | ``` 34 | 35 | Por que falhou? O comando push estava procurando uma imagem denominada dockersamples/101-todo-app, mas 36 |      não encontrou uma. Se você executar `docker image ls`, também não verá uma. 37 | 38 | Para consertar isso, precisamos "marcar" nossa imagem, o que basicamente significa atribuir outro nome a ela. 39 | 40 | 1. Faça o login no Docker Hub utilizando o comando `docker login -u YOUR-USER-NAME`. 41 | 42 | 1. Use o comando `docker tag` para dar à imagem `docker-101` um novo nome. Certifique-se de trocar 43 |     `YOUR-USER-NAME` com seu ID do Docker. 44 | 45 | ```bash 46 | docker tag docker-101 YOUR-USER-NAME/101-todo-app 47 | ``` 48 | 49 | 1. Agora tente o comando push novamente. 50 | 51 | ```bash 52 | docker push YOUR-USER-NAME/101-todo-app 53 | ``` 54 | 55 | ## Executando sua imagem em um novo servidor 56 | 57 | Agora que nossa imagem foi criada e inserida em um registro, vamos tentar executar nossa aplicação em uma nova 58 | instância que nunca viu esse contêiner! 59 | 60 | 1. Volte no PWD, clique em **Add New Instance** para criar uma nova instância. 61 | 62 | 1. Na nova instância, inicie a aplicação recém enviada. 63 | 64 | ```bash 65 | docker run -dp 3000:3000 YOUR-USER-NAME/101-todo-app 66 | ``` 67 | 68 | Você deve ver a imagem sendo baixada, e depois o container sendo criado. 69 | 70 | 1. Clique no link "3000" quando ele aparecer e você deverá ver a aplicação com suas modificações! \o/! 71 | 72 | 73 | ## Recapitulando 74 | 75 | Nesta seção, aprendemos como compartilhar nossas imagens, enviando-as para um repositório. Depois iniciamos uma 76 | nova instância e fomos capazes de executar a imagem recém enviada. Isso é bastante comum em pipelines de IC, 77 | onde o pipeline criará a imagem e a enviará para um registro e, em seguida, para o ambiente de produção 78 | utilizando a versão mais recente da imagem. 79 | 80 | Agora que já descobrimos isso, vamos voltar ao que aprendemos no final da última 81 | seção. Como você deve se lembrar, percebemos que, quando reiniciamos a aplicação, perdemos todos os itens da lista de tarefas. 82 | Obviamente, essa não é uma ótima experiência do usuário, então vamos aprender como podemos persistir os dados depois de 83 | reiniciar! -------------------------------------------------------------------------------- /docs_pt-br/tutorial/compartilhando-nossa-aplicacao/push-command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/tutorial/compartilhando-nossa-aplicacao/push-command.png -------------------------------------------------------------------------------- /docs_pt-br/tutorial/e-depois/index.md: -------------------------------------------------------------------------------- 1 | Embora tenhamos terminado nosso workshop, ainda há MUITO a aprender sobre contêineres! 2 | Não vamos nos aprofundar aqui, mas aqui estão algumas outras áreas para ver a seguir! 3 | 4 | ## Orquestração de Contêineres 5 | 6 | Utilizar contêineres em produção é difícil. Você não logar em uma máquina e simplismente rodar 7 | `docker run` ou `docker-compose up`. Por que não? Bem, o que acontece se os contêineres morrerem? 8 | Como você escala através de várias maquinas? Orquestração de contêineres resolve esse problema. 9 | Ferramentas como Kubernetes, Swarm, Nomad e ECS resolvem esse problema, cada um de uma forma um 10 | pouco diferente do outro. 11 | 12 | A ideia geral é que você tenha "managers" que recebam **estados esperados**. Esse estado pode ser 13 | "Eu quero rodar duas instâncias da minha aplicação web e expor a porta 80". Os managers então olham 14 | para todas as máquinas do cluster e delega o trabalho para os nós "worker". Os managers observam as 15 | mudanças (por exemplo, um contêiner finalizando) e então trabalha para fazer que o **estado atual** reflita 16 | o estado esperado. 17 | 18 | ## Projetos do Cloud Native Computing Foundation 19 | 20 | O CNCF é um local neutro em relação a fornecedores para vários projetos open-source, incluindo Kubernetes, 21 | Prometheus, Envoy, Linkerd, NATS e mais! Você pode ver os [projetos incubados e graduados aqui](https://www.cncf.io/projects/) 22 | e todo o [panorama CNCF aqui](https://landscape.cncf.io/). Existem MUITOS projetos que ajudam a resolver 23 | problemas de monitoramento, logging, segurança, repositório de imagens, mensageria e mais! 24 | 25 | Então, se você é novo no mundo dos contêineres e do desenvolvimento de aplicações cloud-native, seja 26 | bem-vindo! Por favor, conecte-se com a comunidade, faça perguntas e continue aprendendo! Estamos felizes 27 | por ter você aqui! -------------------------------------------------------------------------------- /docs_pt-br/tutorial/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | próximo: app.md 3 | --- 4 | 5 | ## O comando que acabou de executar 6 | 7 | Parabéns! Você iniciou o contêiner para este tutorial! 8 | Vamos primeiro explicar o comando que você acabou de executar. Caso você tenha esquecido, 9 | aqui está o comando: 10 | 11 | ```cli 12 | docker run -d -p 80:80 dockersamples/101-tutorial 13 | ``` 14 | 15 | Você notará algumas opções sendo usadas. Aqui estão mais algumas informações sobre elas: 16 | 17 | - `-d` - executa o contêiner no modo desanexado (em segundo plano) 18 | - `-p 80:80` - mapeia a porta 80 do host para a porta 80 do contêiner 19 | - `dockersamples/101-tutorial` - a imagem em uso 20 | 21 | !!! info "Dica" 22 | Você pode combinar opções com caracteres únicos para diminuir o tamanho do comando completo. 23 | Como exemplo, o comando acima pode ser escrito como: 24 | ``` 25 | docker run -dp 80:80 dockersamples/101-tutorial 26 | ``` 27 | 28 | ## O que é um contêiner? 29 | 30 | Agora que você executou um contêiner, o que _é_ um contêiner? Simplificando, um contêiner é 31 | simplesmente outro processo em sua máquina que foi isolado de todos os outros processos 32 | na máquina host. Esse isolamento aproveita os [namespaces do kernel e cgroups](https://medium.com/@saschagrunert/demystifying-containers-part-i-kernel-space-2c53d6979504), recursos que estão no Linux há muito tempo. O Docker trabalhou para tornar esses recursos acessíveis e fáceis de usar. 33 | 34 | !!! info "Criando containers a partir do zero" 35 | Se você gostaria de ver como os contêineres são construídos a partir do zero, Liz Rice, da Aqua Security 36 | tem uma palestra fantástica na qual ela cria um contêiner do zero usando Go. Ela cria 37 | um contêiner simples, essa palestra não entra em rede, usando imagens para o sistema de arquivos, 38 | e mais. Mas, essa palestra dá um _fantástico_ e profundo mergulho de como as coisas estão funcionando. 39 | 40 | 41 | 42 | ## O que é uma imagem de contêiner? 43 | 44 | Ao executar um contêiner, ele usa um sistema de arquivos isolado. Este sistema de arquivos personalizado é fornecido 45 | por uma **imagem de contêiner**. Como a imagem contém o sistema de arquivos do contêiner, ela deve conter tudo 46 | necessário para executar uma aplicação - todas as dependências, configurações, scripts, binários etc. 47 | A imagem também contém outra configuração para o contêiner, como variáveis de ambiente, 48 | um comando padrão para executar e outros metadados. 49 | 50 | Mais adiante, aprofundaremos nas imagens, abordando tópicos como camadas, melhores práticas e mais. 51 | 52 | !!! info 53 | Se você está familiarizado com o `chroot`, pense em um contêiner como uma versão estendida do `chroot`. O 54 | sistema de arquivos é simplesmente provido pela imagem. Mas, um contêiner adiciona isolamento adicional que não 55 | está disponível ao usar simplesmente chroot. 56 | -------------------------------------------------------------------------------- /docs_pt-br/tutorial/nossa-aplicacao/index.md: -------------------------------------------------------------------------------- 1 | 2 | No restante deste tutorial, trabalharemos com um simples gerenciador de listas de tarefas 3 | que está sendo executado pelo Node. Se você não conhece o Node, não se preocupe! Nenhuma experiência real em JavaScript é necessária! 4 | 5 | Nesse ponto, sua equipe de desenvolvimento é bem pequena e você está simplesmente 6 | criando um aplicativo para provar seu MVP (produto mínimo viável). Você quer 7 | mostrar como funciona e o que é capaz de fazer sem precisar 8 | pensar em como isso funcionará para uma equipe grande, vários desenvolvedores, etc. 9 | 10 | ![Screenshot do gerenciador de lista de tarefas](todo-list-sample.png){: style="width:50%;" } 11 | { .text-center } 12 | 13 | ## Colocando nosso App no PWD 14 | 15 | Antes de podermos executar a aplicação, precisamos inserir o código fonte da aplicação 16 | no ambiente Play with Docker. Para projetos reais, você pode clonar o repositório. Mas 17 | nesse caso você fará o upload de um arquivo ZIP. 18 | 19 | 1. [Faça o download do arquivo zip](/assets/app.zip) e faça o upload para o Play with Docker. Dica, 20 | você pode arrastar e soltar o zip (ou qualquer outro arquivo) no tela do terminal do PWD 21 | 22 | 1. Extraia o conteúdo do arquivo zip no PWD. 23 | 24 | ```bash 25 | unzip app.zip 26 | ``` 27 | 28 | 1. Mude seu diretório para a nova pasta 'app' 29 | 30 | ```bash 31 | cd app/ 32 | ``` 33 | 34 | 1. Nesse diretório você deve ver uma simples aplicação baseada em Node. 35 | 36 | ```bash 37 | ls 38 | package.json spec src yarn.lock 39 | ``` 40 | 41 | ## Criando a imagem do contêiner da aplicação 42 | 43 | Para criar a imagem precisamos usar um `Dockerfile`. Um Dockerfile 44 | é simplesmente um script em texto de instruções que são usadas para 45 | criar a imagem do contêiner. Se você já criou Dockerfiles antes, poderá 46 | veja algumas falhas no Dockerfile abaixo. Mas não se preocupe! Nós vamos examiná-los. 47 | 48 | 1. Crie um arquivo de nome Dockerfile com o seguinte conteúdo. 49 | 50 | ```dockerfile 51 | FROM node:10-alpine 52 | WORKDIR /app 53 | COPY . . 54 | RUN yarn install --production 55 | CMD ["node", "/app/src/index.js"] 56 | ``` 57 | 58 | 1. Construa a imagem do contêiner usando o comando `docker build`. 59 | 60 | ```bash 61 | docker build -t docker-101 . 62 | ``` 63 | 64 | Este comando usou o Dockerfile para criar uma nova imagem de contêiner. Você pode 65 | ter notado que muitas "camadas" foram baixadas. Isso ocorre porque definimos no Dockerfile 66 | que queríamos iniciar a partir da imagem `node: 10-alpine`. Mas como nós 67 | não tinhamos essa imagem em nossa máquina ela precisava ser baixada. 68 | 69 | Depois disso, copiamos a nossa aplicação e usamos `yarn` para instalar as dependências. 70 | A diretiva `CMD` especifica o comando padrão a ser executado ao iniciar um contêiner 71 | desta imagem. 72 | 73 | ## Iniciando um contêiner de aplicação 74 | 75 | Agora que temos uma imagem, vamos executar a aplicação! Para fazer isso, usaremos o comando 76 | `docker run` (lembra-se dele anteriormente?). 77 | 78 | 1. Inicie seu contêiner usando o comando `docker run`: 79 | 80 | ```bash 81 | docker run -dp 3000:3000 docker-101 82 | ``` 83 | 84 | Lembra das opções `-d` e `-p`? Estamos executando o novo contêiner no modo "desanexado" (no 85 | background) e criando um mapeamento entre a porta 3000 do host e a porta 3000 do contêiner. 86 | 87 | 1. Abra a aplicação clicando no link "3000" que apareceu na parte superior da interface do PWD. Uma vez aberto, 88 | você deve ter uma lista de tarefas vazia! 89 | 90 | ![Lista de tarefas vazia](todo-list-empty.png){: style="width:450px;margin-top:20px;"} 91 | {: .text-center } 92 | 93 | 1. Vá em frente e adicione um ou dois itens e veja se ele funciona conforme o esperado. Você pode marcar itens como 94 | completo e remover itens. 95 | 96 | Nesse ponto você tem em execução um gerenciador de lista de tarefas com poucos itens, tudo construído por você! 97 | Agora vamos fazer algumas alterações e aprender sobre o gerenciamento de nossos contêineres. 98 | 99 | ## Recapitulando 100 | 101 | Nesta seção, aprendemos o básico sobre a construção de uma imagem de contêiner e criamos um 102 | Dockerfile para fazer isso. Depois que construímos uma imagem, iniciamos o contêiner e vimos a aplicação em execução! 103 | 104 | Em seguida, faremos uma modificação e aprenderemos a atualizar nossa aplicação em execução 105 | com uma nova imagem. Ao longo do caminho, aprenderemos alguns outros comandos úteis. 106 | -------------------------------------------------------------------------------- /docs_pt-br/tutorial/nossa-aplicacao/todo-list-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/tutorial/nossa-aplicacao/todo-list-empty.png -------------------------------------------------------------------------------- /docs_pt-br/tutorial/nossa-aplicacao/todo-list-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/tutorial/nossa-aplicacao/todo-list-sample.png -------------------------------------------------------------------------------- /docs_pt-br/tutorial/persistindo-nossos-dados/index.md: -------------------------------------------------------------------------------- 1 | 2 | Caso não tenha notado, nossa lista de tarefas está sendo apagada toda vez que rodamos 3 | o container. Porque isso? Vamos mergulhar em como o container está trabalhando. 4 | 5 | ## O sistema de arquivos de um container 6 | 7 | Quando um container roda, ele utiliza as várias camadas de uma imagem como seu sistema de arquivos. 8 | Cada container também recebe o seu próprio "espaço de rascunho" para criar/atualizar/remover arquivos. 9 | Quaisquer mudanças não serão vistas em outro container, _mesmo se_ eles estiverem usando a mesma imagem. 10 | 11 | 12 | ### Vendo isso na prática 13 | 14 | Para ver isso em ação, iremos iniciar dois containers e criar um arquivo em cada. 15 | O que você verá é que os arquivos criados em um container não estarão disponíveis no outro. 16 | 17 | 1. Inicie um container `ubuntu` que irá criar um arquivo chamado `/data.txt` com um número aleatório 18 | entre 1 e 10000. 19 | 20 | ```bash 21 | docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null" 22 | ``` 23 | 24 | Caso esteja se perguntando sobre o comando, nós estamos iniciando um shell bash e invocando 25 | dois comandos (por isso o `&&`). A primeira parte pega um número aleatório e escreve ele em `/data.txt`. 26 | O segundo comando está simplesmente "olhando" o arquivo para manter o container rodando. 27 | 28 | 1. Para validar, podemos ver a saída `exec`utando dentro do container. Para fazer isso, você precisa pegar o ID do container (use `docker ps` para pegá-lo). 29 | 30 | ```bash 31 | docker exec cat /data.txt 32 | ``` 33 | 34 | Você deve ver um número aleatório! 35 | 36 | 1. Agora, vamos iniciar outro container `ubuntu` (a mesma imagem) e veremos que não temos o mesmo arquivo. 37 | 38 | ```bash 39 | docker run -it ubuntu ls / 40 | ``` 41 | 42 | E olhe! Não há qualquer arquivo `data.txt` lá! Isso acontece porque ele foi escrito no espaço de rascunho apenas do primeiro container. 43 | 44 | 1. Vá em frente e remova o primeiro container usando o comando `docker rm -f`. 45 | 46 | 47 | ## Volumes de container 48 | 49 | Com o experimento anterior, vimos que cada container é efetivamente somente leitura. Enquanto containers podem 50 | criar, atualizar e apagar arquivos, estas mudanças são perdidas quando o container é removido e são isoladas naquele container. 51 | Com volumes nós podemos mudar isso. 52 | 53 | [Volumes](https://docs.docker.com/storage/volumes/) provêem a habilidade de conectar caminhos específicos do sistema de arquivos 54 | do container de volta para a máquina hospedeira (host). Se um diretório no container é montado, mudanças naquele diretório também são vistas no host. Se montarmos aquele mesmo diretório ao reiniciar um container, veremos os mesmos aquivos. 55 | 56 | Há dois tipos principais de volumes. Eventualmente usaremos ambos, mas vamos começar com **volumes nomeados**. 57 | 58 | 59 | 60 | ## Persistindo nossos dados de tarefas 61 | 62 | Por padrão, o aplicativo de tarefas grava seus dados em um [Banco de dados SQLite](https://www.sqlite.org/index.html) em 63 | `/etc/todos/todo.db`. Se você não está familizarizado com SQLite, não se preocupe! É simplesmente um banco de dados relacional 64 | no qual todos os dados são gravados em um único arquivo. Apesar de que isso não é o melhor para aplicações de grande escala, 65 | funciona muito bem para pequenas demos. Nós vamos falar sobre como mudar isto para uma engine de banco dados real depois. 66 | 67 | Com o banco de dados sendo um único arquivo, se persistirmos este arquivo no host e tornarmos ele disponível para o próximo contêiner, 68 | ele deve conseguir continuar de onde o anterior parou. Ao criar um volume e anexar (geralmente chamado "montar") ele ao diretório que 69 | os dados estão armazenados, nós conseguimos persistir os dados. Como o nosso container escreve no arquivo `todo.db`, ele será persistido 70 | para o host no volume. 71 | 72 | Como mencionado, nós vamos usar um **volume nomeado**. Pense em um volume nomeado simplesmente como um bucket de dados. 73 | O Docker irá manter a localizalização física no disco e você só precisa lembrar o nome do volume. 74 | Cada vez que você usar o volume, o Docker irá garantir que os dados corretos sejam providos. 75 | 76 | 1. Crie um volume usando o comando `docker volume create`. 77 | 78 | ```bash 79 | docker volume create todo-db 80 | ``` 81 | 82 | 1. Inicie o container de tarefas, mas adicione a flag `-v` para especificar uma montagem de volume. Vamos usar o volume nomeado 83 | montá-lo em `/etc/todos/`, que irá capturar todos os arquivos criados neste caminho. 84 | 85 | ```bash 86 | docker run -dp 3000:3000 -v todo-db:/etc/todos docker-101 87 | ``` 88 | 89 | 1. Assim que o container iniciar, abra o aplicativo e adicione alguns itens à sua lista de tarefas. 90 | 91 | ![Itens adicionar à lista de tarefas.](items-added.png){: style="width: 55%; " } 92 | {: .text-center } 93 | 94 | 1. Remova o container do aplicativo de tarefas. Use `docker ps` para pegar o ID e então `docker rm -f ` para removê-lo. 95 | 96 | 1. Inicie um container novo usando o mesmo comando acima. 97 | 98 | 1. Abra o aplicativo. Você deve ver seus itens ainda na sua lista! 99 | 100 | 1. Vá em frente e remova o container quando tiver terminado de checar a sua lista. 101 | 102 | \o/! Você agora aprendeu como persistir dados! 103 | 104 | !!! info "Dica" 105 | Apesar de volumes nomeados e volumes montados diretamente no host (vamos falar delas em um minuto) serem os 2 tipos principais de volumes 106 | suportados em uma instalação padrão da Docker engine, há muito mais plugins de driver de volume disponíveis para suportar 107 | NFS, SFTP, NetApp e mais! Isto será especialmente importante quando você começar a rodar containers em múltiplos hosts em 108 | um ambiente clusterizado com Swarm, Kubernetes, etc. 109 | 110 | 111 | ## Mergulhando no nosso Volume 112 | 113 | Muitas pessoas frequentemente perguntam "Onde o Docker _realmente_ guarda meus dados quando eu uso um volume nomeado?". Se você 114 | quiser saber, pode usar o comando `docker volume inspect`. 115 | 116 | ```bash 117 | docker volume inspect todo-db 118 | [ 119 | { 120 | "CreatedAt": "2019-09-26T02:18:36Z", 121 | "Driver": "local", 122 | "Labels": {}, 123 | "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", 124 | "Name": "todo-db", 125 | "Options": {}, 126 | "Scope": "local" 127 | } 128 | ] 129 | ``` 130 | 131 | O `Mountpoint` é a localização real no disco onde os dados estão armazenados. Note que na maioria das máquinas, você precisará de acesso 132 | como root para acessar este diretório no host. Mas, é onde está! 133 | 134 | 135 | ## Recapitulando 136 | 137 | Neste ponto temos uma aplicação funcionando que pode sobreviver à reinicializações! Podemos agora mostrá-la aos nossos investidores e torcer que eles consigam entender a nossa visão! 138 | 139 | Porém, nós vimos antes que reconstruir imagens para cada mudança toma um pouco de tempo. Tem de haver uma forma melhor de fazer mudanças, certo? Com os pontos de montagens (que nós comentamos antes), há uma forma melhor! Vamos dar uma olhada nisso agora! 140 | -------------------------------------------------------------------------------- /docs_pt-br/tutorial/persistindo-nossos-dados/items-added.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/tutorial/persistindo-nossos-dados/items-added.png -------------------------------------------------------------------------------- /docs_pt-br/tutorial/utilizando-montagens-bind/index.md: -------------------------------------------------------------------------------- 1 | 2 | No capítulo anterior nós falamos sobre e usamos um **volume nomeado** para persistir os dados no nosso banco de dados. 3 | Volumes nomeados são ótimo se nós apenas precisamos guardar dados, já que não precisamos nos preocupar com _onde_ os dados são guardados. 4 | 5 | Com **pontos de montagem** nós controlamos o local exato da montagem no host. Nós podemos usar isso para persistir dados, 6 | mas geralmente ele é utilizado para prover dados adicionais dentro do container. Quando trabalhando com um aplicação, nós 7 | podemos utilizar um ponto de montagem para montar o nosso código fonte dentro do container e deixar ele ver as mudanças do código, 8 | responder e nos deixar ver as mudanças na mesma hora. 9 | 10 | Para aplicações baseadas em Node.js, [nodemon](https://npmjs.com/package/nodemon) é uma ótima ferramenta para cuidar por mudanças de 11 | código e então reiniciar a aplicação. Há ferramentas equivalentes na maioria das linguagens e frameworks. 12 | 13 | ## Comparação rápida entre tipos de Volume 14 | 15 | Pontos de montagem e volumes nomeados são os dois principais tipos de volumes que vem com a engine Docker. Porem, drivers de volume adicionais estão disponíveis para suportar outros casos de uso ([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), e mais). 16 | 17 | | | Volumes nomeados | Pontos de montagem | 18 | | - | ------------- | ----------- | 19 | | Caminho no host | Docker decide | Você controla | 20 | | Exemplo de montagem (usando `-v`)| meu-volume:/usr/local/data | /caminho/para/os/dados:/usr/local/data | 21 | | Popula o novo volume com conteúdo do container | Sim | Não | 22 | | Suporta drivers de Volume | Sim | Não | 23 | 24 | 25 | ## Iniciando um Container em "Modo-Dev" 26 | 27 | Para rodar nosso container com suporte a um workflow de desenvolvimento, faremos o seguinte: 28 | 29 | - Montar nosso código fonte dentro do container 30 | - Instalar todas dependências, incluindo as dependências "dev" 31 | - Iniciar o nodemon para cuidar por mudanças no sistema de arquivos 32 | 33 | Então, bora lá! 34 | 35 | 1. Garanta que você não tem nenhum dos containers `docker-101` rodando. 36 | 37 | 1. Rode o seguinte comando. Vamos explicar o que está acontecendo depois: 38 | 39 | ```bash 40 | docker run -dp 3000:3000 \ 41 | -w /app -v $PWD:/app \ 42 | node:10-alpine \ 43 | sh -c "yarn install && yarn run dev" 44 | ``` 45 | 46 | - `-dp 3000:3000` - mesma coisa de antes. Roda no modo desanexado (background) e cria um mapeamento de porta 47 | - `-w /app` - define o "diretório de trabalho" ou, o diretório onde de onde o comando será rodado 48 | - `node:10-alpine` - a imagem para usar. Note que é a imagem base para o nosso aplicativo no Dockerfile 49 | - `sh -c "yarn install && yarn run dev"` - o comando. Nós estamos iniciando um shell usando `sh` (alpine não tem `bash`) e 50 | rodando `yarn install` para instalar _todas_ dependências e então rodando `yarn run dev`. Se olhar no arquivo `package.json`, 51 | veremos que o script `dev` está iniciando o `nodemon`. 52 | 53 | 1. Você pode olhar os logs usando `docker logs -f `. VOcê vai saber que está pronto quando ver isso... 54 | 55 | ```bash 56 | docker logs -f 57 | $ nodemon src/index.js 58 | [nodemon] 1.19.2 59 | [nodemon] to restart at any time, enter `rs` 60 | [nodemon] watching dir(s): *.* 61 | [nodemon] starting `node src/index.js` 62 | Using sqlite database at /etc/todos/todo.db 63 | Listening on port 3000 64 | ``` 65 | 66 | Quando terminar de olhar os logs, saia pressionando `Ctrl`+`C`. 67 | 68 | 1. Agora, vamos fazer a mudança no aplicativo. No arquivo `src/static/js/app.js` vamos mudar o botão "Add Item" para 69 | simplesmente dizer "Add". Essa mudança será na linha 109 70 | 71 | ```diff 72 | - {submitting ? 'Adding...' : 'Add Item'} 73 | + {submitting ? 'Adding...' : 'Add'} 74 | ``` 75 | 76 | 1. Apenas atualize a página (ou abra ela) e você verá a mudança refletida no browser quase imediatamente. Pode levar uns segundos 77 | para o servidor Node reiniciar, então se aparecer um erro, apenas atualize depois de alguns segundos. 78 | 79 | ![Screenshot do nome do botão Add atualizado](updated-add-button.png){: style="width:75%;"} 80 | {: .text-center } 81 | 82 | 1. Sinta-se livre para fazer quaisquer outras mudanças que você gostaria de fazer. Quando tiver terminado, pare o container e construa sua nova imagem usando `docker build -t docker-101 .`. 83 | 84 | 85 | Usar pontos de montam é _muito_ comum para setups de desenvolvimento local. A vantagem é que a máquina dev não precisa ter 86 | todas as ferramentas e ambientes de build instalados. Com apenas um `docker run`, o ambiente de desenvolvimento é puxado e fica pronto 87 | para ser usado. Vamos falar sobre Docker Compose em um passo futuro, já que isso simplifica nossos comandos (nós já estamos usando várias flags). 88 | 89 | ## Recapitulando 90 | 91 | Neste ponto, já podemos persistir nosso banco de dados e responder rapidamente às necessidades e demandas dos nossos investidores 92 | e fundadores. \o/! 93 | Mas, Adivinha? Recebemos grandes notícias! 94 | 95 | **Seu projeto foi selecionado para desenvolvimento futuro!** 96 | 97 | Para se preparar para produção, precisamos migrar nossa base de dados de SQLite para algo que possa escalar um pouco melhor. 98 | Para simplicidade, vamos manter com uma base de dados relacional e mudar a nossa aplicação para usar MySQL. Mas, como devemos 99 | rodar o MySQL? Como vamos fazer com que os containers falem entre si? Vamos falar sobre isso agora! 100 | -------------------------------------------------------------------------------- /docs_pt-br/tutorial/utilizando-montagens-bind/updated-add-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dockersamples/101-tutorial/05be8bd9e9ecea7fdae60a5dffaf9f880d21b38b/docs_pt-br/tutorial/utilizando-montagens-bind/updated-add-button.png -------------------------------------------------------------------------------- /mkdocs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "en" : { 3 | "language_code": "en", 4 | "site_description": "A tutorial for learning to use Docker", 5 | "tutorial_dir_name": "tutorial", 6 | "nav": { 7 | "Tutorial": { 8 | "title": "Tutorial" 9 | }, 10 | "Getting Started": { 11 | "title" : "Getting Started" 12 | }, 13 | "Our Application": { 14 | "title": "Our Application", 15 | "dir_name": "our-application" 16 | }, 17 | "Updating our App": { 18 | "title": "Updating our App", 19 | "dir_name": "updating-our-app" 20 | }, 21 | "Sharing our App": { 22 | "title": "Sharing our App", 23 | "dir_name": "sharing-our-app" 24 | }, 25 | "Persisting our DB": { 26 | "title": "Persisting our DB", 27 | "dir_name": "persisting-our-data" 28 | }, 29 | "Using Bind Mounts": { 30 | "title": "Using Bind Mounts", 31 | "dir_name": "using-bind-mounts" 32 | }, 33 | "Multi-Container Apps": { 34 | "title": "Multi-Container Apps", 35 | "dir_name": "multi-container-apps" 36 | }, 37 | "Using Docker Compose": { 38 | "title": "Using Docker Compose", 39 | "dir_name": "using-docker-compose" 40 | }, 41 | "Image Building Best Practices": { 42 | "title": "Image Building Best Practices", 43 | "dir_name": "image-building-best-practices" 44 | }, 45 | "What Next?": { 46 | "title": "What Next?", 47 | "dir_name": "what-next" 48 | }, 49 | "PWD Tips": { 50 | "title": "PWD Tips", 51 | "dir_name": "pwd-tips" 52 | } 53 | } 54 | }, 55 | "pt-br" : { 56 | "language_code": "pt", 57 | "site_description": "Tutorial para aprender usar Docker", 58 | "tutorial_dir_name": "tutorial", 59 | "nav": { 60 | "Tutorial": { 61 | "title": "Tutorial" 62 | }, 63 | "Getting Started": { 64 | "title" : "Começando" 65 | }, 66 | "Our Application": { 67 | "title": "Nossa Aplicação", 68 | "dir_name": "nossa-aplicacao" 69 | }, 70 | "Updating our App": { 71 | "title": "Atualizando nossa Aplicação", 72 | "dir_name": "atualizando-nossa-aplicacao" 73 | }, 74 | "Sharing our App": { 75 | "title": "Compartilhando nossa Aplicação", 76 | "dir_name": "compartilhando-nossa-aplicacao" 77 | }, 78 | "Persisting our DB": { 79 | "title": "Persistindo nosso BD", 80 | "dir_name": "persistindo-nossos-dados" 81 | }, 82 | "Using Bind Mounts": { 83 | "title": "Utilizando Montagens Bind", 84 | "dir_name": "utilizando-montagens-bind" 85 | }, 86 | "Multi-Container Apps": { 87 | "title": "Aplicações Multi-Contêiner", 88 | "dir_name": "aplicacoes-multi-conteiner" 89 | }, 90 | "Using Docker Compose": { 91 | "title": "Utilizando Docker Compose", 92 | "dir_name": "utilizando-docker-compose" 93 | }, 94 | "Image Building Best Practices": { 95 | "title": "Melhores Práticas para Construir uma Imagem", 96 | "dir_name": "melhores-praticas-construir-imagem" 97 | }, 98 | "What Next?": { 99 | "title": "E Depois?", 100 | "dir_name": "e-depois" 101 | }, 102 | "PWD Tips": { 103 | "title": "Dicas PWD", 104 | "dir_name": "dicas-pwd" 105 | } 106 | } 107 | }, 108 | "es" : { 109 | "language_code": "es", 110 | "site_description": "Un tutorial para aprender a usar Docker", 111 | "tutorial_dir_name": "tutorial", 112 | "nav": { 113 | "Tutorial": { 114 | "title": "Tutorial" 115 | }, 116 | "Getting Started": { 117 | "title" : "Primeros pasos" 118 | }, 119 | "Our Application": { 120 | "title": "Nuestra aplicación", 121 | "dir_name": "nuestra-aplicacion" 122 | }, 123 | "Updating our App": { 124 | "title": "Actualizando nuestra aplicación", 125 | "dir_name": "actualizando-nuestra-aplicacion" 126 | }, 127 | "Sharing our App": { 128 | "title": "Compartiendo nuestra aplicación", 129 | "dir_name": "compartiendo-nuestra-aplicacion" 130 | }, 131 | "Persisting our DB": { 132 | "title": "Persistiendo nuestra base de datos", 133 | "dir_name": "persistiendo-nuestra-base-de-datos" 134 | }, 135 | "Using Bind Mounts": { 136 | "title": "Usando Bind Mounts", 137 | "dir_name": "usando-bind-mounts" 138 | }, 139 | "Multi-Container Apps": { 140 | "title": "Aplicaciones Multi-Contenedor", 141 | "dir_name": "aplicaciones-multi-contenedor" 142 | }, 143 | "Using Docker Compose": { 144 | "title": "Usando Docker Compose", 145 | "dir_name": "usando-docker-compose" 146 | }, 147 | "Image Building Best Practices": { 148 | "title": "Buenas prácticas para construcción de imagenes", 149 | "dir_name": "buenas-practicas-para-construccion-de-imagenes" 150 | }, 151 | "What Next?": { 152 | "title": "¿Qué sigue?", 153 | "dir_name": "que-sigue" 154 | }, 155 | "PWD Tips": { 156 | "title": "Consejos para usar PWD", 157 | "dir_name": "consejos-para-usar-pwd" 158 | } 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Docker 101 2 | site_description: A tutorial for learning to use Docker 3 | site_author: Docker 4 | # site_url: https://squidfunk.github.io/mkdocs-material/ 5 | 6 | # Repository 7 | repo_name: dockersamples/101-tutorial 8 | repo_url: https://github.com/dockersamples/101-tutorial 9 | edit_uri: "" 10 | 11 | # Copyright 12 | copyright: 'Copyright © 2019 Docker' 13 | 14 | # Configuration 15 | theme: 16 | name: material 17 | language: en 18 | palette: 19 | primary: blue 20 | accent: blue 21 | font: 22 | text: Roboto 23 | code: Roboto Mono 24 | favicon: assets/images/favicon.png 25 | logo: 'images/docker-labs-logo.svg' 26 | 27 | extra_css: 28 | - css/styles.css 29 | - css/dark-mode.css 30 | 31 | extra_javascript: 32 | - https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js 33 | - js/jquery.raptorize.1.0.js 34 | - js/custom.js 35 | 36 | # Plugins 37 | plugins: 38 | - search 39 | - minify: 40 | minify_html: true 41 | 42 | # Customization 43 | extra: 44 | social: 45 | - type: github-alt 46 | link: https://github.com/dockersamples/101-tutorial 47 | 48 | # Extensions 49 | markdown_extensions: 50 | - meta 51 | - markdown.extensions.admonition 52 | - markdown.extensions.codehilite: 53 | guess_lang: false 54 | - markdown.extensions.footnotes 55 | - markdown.extensions.toc: 56 | permalink: true 57 | - pymdownx.betterem: 58 | smart_enable: all 59 | - pymdownx.caret 60 | - pymdownx.critic 61 | - pymdownx.details 62 | - pymdownx.inlinehilite 63 | - pymdownx.magiclink: 64 | repo_url_shorthand: true 65 | user: dockersamples 66 | repo: 101-tutorial 67 | - pymdownx.mark 68 | - pymdownx.smartsymbols 69 | - pymdownx.superfences 70 | - pymdownx.tasklist: 71 | custom_checkbox: true 72 | - pymdownx.tilde 73 | - attr_list 74 | 75 | # Page tree 76 | nav: 77 | - Tutorial: 78 | - Getting Started: tutorial/index.md 79 | - Our Application: tutorial/our-application/index.md 80 | - Updating our App: tutorial/updating-our-app/index.md 81 | - Sharing our App: tutorial/sharing-our-app/index.md 82 | - Persisting our DB: tutorial/persisting-our-data/index.md 83 | - Using Bind Mounts: tutorial/using-bind-mounts/index.md 84 | - Multi-Container Apps: tutorial/multi-container-apps/index.md 85 | - Using Docker Compose: tutorial/using-docker-compose/index.md 86 | - Image Building Best Practices: tutorial/image-building-best-practices/index.md 87 | - What Next?: tutorial/what-next/index.md 88 | - PWD Tips: pwd-tips/index.md 89 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | pygments 4 | pymdown-extensions -------------------------------------------------------------------------------- /verify.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const data = require('./mkdocs-config.json'); 3 | 4 | const errors = []; 5 | 6 | function assertDefined(property, languageCode, path) { 7 | if (property === undefined) 8 | errors.push(`${languageCode}: Missing property ${path}`); 9 | } 10 | 11 | function assertNavDefined(navRoot, key, languageCode, dirRequired = true) { 12 | if (navRoot[key] === undefined) 13 | return errors.push(`${languageCode}: Missing nav spec for ${key}`); 14 | 15 | const p = navRoot[key]; 16 | assertDefined(p.title, languageCode, `nav['${key}'].title`); 17 | 18 | if (dirRequired) { 19 | assertDefined(p.dir_name, languageCode, `nav['${key}'].dir_name`); 20 | const path = (key === 'PWD Tips') ? `docs_${languageCode}/` : `docs_${languageCode}/${data[languageCode].tutorial_dir_name}/`; 21 | if (p.dir_name && !fs.existsSync(`${path}${p.dir_name}`)) 22 | errors.push(`${languageCode}: Unable to find directory ${path}${p.dir_name}`); 23 | } 24 | } 25 | 26 | Object.keys(data).forEach(languageCode => { 27 | if (!fs.existsSync(`docs_${languageCode}`)) 28 | errors.push(`${languageCode}: Missing directory 'docs_${languageCode}'`); 29 | 30 | const languageData = data[languageCode]; 31 | assertDefined(languageData.language_code, languageCode, 'language_code'); 32 | assertDefined(languageData.site_description, languageCode, 'site_description'); 33 | 34 | assertDefined(languageData.nav, languageCode, 'nav'); 35 | assertNavDefined(languageData.nav, 'Tutorial', languageCode, false); 36 | assertNavDefined(languageData.nav, 'Getting Started', languageCode, false); 37 | assertNavDefined(languageData.nav, 'Our Application', languageCode); 38 | assertNavDefined(languageData.nav, 'Updating our App', languageCode); 39 | assertNavDefined(languageData.nav, 'Sharing our App', languageCode); 40 | assertNavDefined(languageData.nav, 'Persisting our DB', languageCode); 41 | assertNavDefined(languageData.nav, 'Using Bind Mounts', languageCode); 42 | assertNavDefined(languageData.nav, 'Multi-Container Apps', languageCode); 43 | assertNavDefined(languageData.nav, 'Using Docker Compose', languageCode); 44 | assertNavDefined(languageData.nav, 'Image Building Best Practices', languageCode); 45 | assertNavDefined(languageData.nav, 'What Next?', languageCode); 46 | assertNavDefined(languageData.nav, 'PWD Tips', languageCode); 47 | }); 48 | 49 | if (errors.length > 0) { 50 | console.error('Found the following errors:'); 51 | errors.forEach((m) => console.error(`- ${m}`)); 52 | process.exit(1); 53 | } 54 | console.log("All good to go!"); --------------------------------------------------------------------------------