{"id":69351,"date":"2020-02-18T14:44:06","date_gmt":"2020-02-18T11:44:06","guid":{"rendered":"https:\/\/prohoster.info\/blog\/blog_prohoster\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker"},"modified":"2020-03-03T16:15:00","modified_gmt":"2020-03-03T13:15:00","slug":"sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","status":"publish","type":"post","link":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","title":{"rendered":"Oprettelse af CI\/CD-k\u00e6de og automatisering af arbejde med Docker","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Jeg lavede mine f\u00f8rste hjemmesider i slutningen af 90'erne. Dengang var det meget enkelt at f\u00e5 dem til at fungere. Der var en Apache-server p\u00e5 en eller anden delt hosting, og man kunne logge ind p\u00e5 denne server via FTP ved at skrive noget i browserens adresselinje s\u00e5som <code>ftp:\/\/ftp.example.com<\/code>. S\u00e5 skulle man indtaste sit brugernavn og kodeord og uploade filer til serveren. Tiderne var anderledes, alt var lettere dengang end nu.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/488668\/\"><img decoding=\"async\" alt=\"Oprettelse af CI\/CD-k\u00e6de og automatisering af arbejde med Docker\" src=\"\/wp-content\/uploads\/2020\/02\/0861cdd82770ef4695ffac7a09777c52.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><\/p>\n<p>I de to \u00e5rtier, der er g\u00e5et siden, er alt blevet ret meget \u00e6ndret. Hjemmesiderne er blevet mere komplekse, og f\u00f8r de g\u00e5r i produktion, skal de samles. En enkelt server er blevet til mange servere, der arbejder bag load balancere, og brugen af versionskontrolsystemer er blevet almindeligt.<\/p>\n<p>Til mit personlige projekt havde jeg en speciel konfiguration. Og jeg vidste, at jeg havde brug for muligheden for hurtigt at implementere hjemmesiden i produktionen med blot \u00e9n handling: at skrive koden til en gren <code>master<\/code> p\u00e5 GitHub. Derudover vidste jeg, at jeg ikke \u00f8nskede at h\u00e5ndtere et enormt Kubernetes-klynge for at f\u00e5 mit lille webapp til at fungere, eller bruge Docker Swarm, eller administrere en samling servere med pods, agenter og andre komplekse ting. For at n\u00e5 mit m\u00e5l om maksimal forenkling af arbejdet m\u00e5tte jeg l\u00e6re om CI\/CD.<\/p>\n<p>Hvis du har et lille projekt (i dette tilf\u00e6lde en Node.js-projekt) og gerne vil vide, hvordan man automatiserer implementeringen af dette projekt, samtidig med at det som opbevares i depotet n\u00f8jagtigt svarer til det, der k\u00f8rer i produktionen, s\u00e5 tror jeg, at du kan finde denne artikel interessant.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Foruds\u00e6tninger<\/h2>\n<p>\nDet forventes, at l\u00e6seren af denne artikel har grundl\u00e6ggende kendskab til at arbejde med kommandolinjen og skrive Bash-scripts. Derudover vil der v\u00e6re behov for konti <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a><\/noindex> og <noindex><a rel=\"nofollow\" href=\"https:\/\/hub.docker.com\/\">Docker Hub<\/a><\/noindex>.<\/p>\n<h2>M\u00e5l<\/h2>\n<p>\nJeg vil ikke sige, at denne artikel uden forbehold kan kaldes et \"vejledning.\" Det er n\u00e6rmere et dokument, hvor jeg fort\u00e6ller om, hvad jeg har l\u00e6rt, og beskriver den proces for testning og implementering af kode i produktionen, som jeg finder tilfredsstillende, og som udf\u00f8res i et enkelt automatiseret trin.<\/p>\n<p>Her er, hvordan min arbejdsproces i sidste ende s\u00e5 ud.<\/p>\n<p>For koden sendt til enhver gren af depotet, undtagen <code>master<\/code>, udf\u00f8res f\u00f8lgende handlinger:<\/p>\n<ul>\n<li>Projektet bygges p\u00e5 Travis CI.<\/li>\n<li>Alle enhedstest, integrationstest og end-to-end test k\u00f8res.<\/li>\n<\/ul>\n<p>\nKun for koden, der falder ind <code>master<\/code>, udf\u00f8res f\u00f8lgende:<\/p>\n<ul>\n<li>Alt det, der er n\u00e6vnt ovenfor, plus\u2026<\/li>\n<li>Bygning af et Docker-billede baseret p\u00e5 den nuv\u00e6rende kode, indstillinger og milj\u00f8.<\/li>\n<li>Placerer billedet p\u00e5 Docker Hub.<\/li>\n<li>Tilslutning til produktionsserveren.<\/li>\n<li>Download billedet fra Docker Hub til serveren.<\/li>\n<li>Stop den nuv\u00e6rende container og start en ny baseret p\u00e5 det nye billede.<\/li>\n<\/ul>\n<p>\nHvis du slet ikke ved noget om Docker, billeder og containere \u2014 ingen grund til bekymring. Jeg vil fort\u00e6lle dig alt om det.<\/p>\n<h2>Hvad er CI\/CD?<\/h2>\n<p>\nForkortelsen CI\/CD st\u00e5r for 'continuous integration\/continuous deployment' \u2014 'kontinuerlig integration\/kontinuerlig implementering'.<\/p>\n<h3>\u258dKontinuerlig integration<\/h3>\n<p>\nKontinuerlig integration er en proces, hvor udviklere foretager commits til hovedlageret af kildeteksten i projektet (normalt til grenen <code>master<\/code>). Kvaliteten af koden sikres ved hj\u00e6lp af automatiserede tests.<\/p>\n<h3>\u258dKontinuerlig implementering<\/h3>\n<p>\nKontinuerlig implementering er hyppig automatiseret implementering af koden i produktionen. Den anden del af forkortelsen CI\/CD bliver nogle gange forklaret som 'continuous delivery' ('kontinuerlig levering'). Dette er i det store hele det samme som 'kontinuerlig implementering', men 'kontinuerlig levering' indeb\u00e6rer behovet for manuel bekr\u00e6ftelse af \u00e6ndringerne, f\u00f8r processen for implementering af projektet startes.<\/p>\n<h2>Getting Started<\/h2>\n<p>\nApplikationen, som jeg har l\u00e6rt alt dette p\u00e5, hedder <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/taniarascia\/takenote\">TakeNote<\/a><\/noindex>. Dette er et webprojekt, jeg arbejder p\u00e5, designet til at tage noter. F\u00f8rst fors\u00f8gte jeg at lave <noindex><a rel=\"nofollow\" href=\"https:\/\/jamstack.org\/\">et JAMStack<\/a><\/noindex>-projekt, eller blot en frontend-applikation uden server, for at udnytte de standardmuligheder for hosting og implementering af projekter, som tilbydes af <noindex><a rel=\"nofollow\" href=\"https:\/\/www.netlify.com\/\">Netlify<\/a><\/noindex>. I takt med at kompleksiteten af applikationen voksede, havde jeg brug for at skabe dens serverdel, hvilket bet\u00f8d, at jeg skulle udvikle min egen strategi for automatiseret integration og automatiseret implementering af projektet.<\/p>\n<p>I mit tilf\u00e6lde er applikationen en Express-server, der k\u00f8rer i Node.js-milj\u00f8et, betjener en enkelt-side React-applikation og underst\u00f8tter et sikkert server-API. Denne arkitektur f\u00f8lger en strategi, man kan finde i <noindex><a rel=\"nofollow\" href=\"https:\/\/www.taniarascia.com\/full-stack-cookies-localstorage-react-express\/\">denne<\/a><\/noindex> vejledning til fullstack-autentifikation.<\/p>\n<p>Jeg har r\u00e5df\u00f8rt mig med <noindex><a rel=\"nofollow\" href=\"http:\/\/qualitytesting.tech\/\">en anden<\/a><\/noindex>, som er en ekspert i automatisering, og jeg spurgte ham, hvad jeg skulle g\u00f8re for at f\u00e5 det hele til at fungere, som jeg \u00f8nskede. Han gav mig en id\u00e9 om, hvordan den automatiserede arbejdsproces skulle se ud, som beskrevet i afsnittet 'M\u00e5l' i denne artikel. At jeg satte mig s\u00e5danne m\u00e5l bet\u00f8d, at jeg var n\u00f8dt til at finde ud af, hvordan man bruger Docker.<\/p>\n<h2>Docker<\/h2>\n<p>\nDocker er et v\u00e6rkt\u00f8j, der ved hj\u00e6lp af containeriseringsteknologi g\u00f8r det muligt at distribuere applikationer let samt udf\u00f8re deres deployment og k\u00f8rsel i det samme milj\u00f8, selvom Docker-platformen k\u00f8rer i forskellige milj\u00f8er. For at komme i gang havde jeg brug for at f\u00e5 adgang til Docker Command Line Interface (CLI)-v\u00e6rkt\u00f8jerne. <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/install\/\">Instruktionen<\/a><\/noindex> til installation af Docker kan ikke kaldes meget klar og forst\u00e5elig, men den giver information om, at for at tage det f\u00f8rste skridt mod installation, skal man downloade Docker Desktop (til Mac eller Windows).<\/p>\n<p>Docker Hub er omtrent det samme som <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/\">GitHub<\/a><\/noindex> for git-repositories, eller et register <noindex><a rel=\"nofollow\" href=\"https:\/\/www.npmjs.com\/\">npm<\/a><\/noindex> for JavaScript-pakker. Det er et online-repository for Docker-images. Det er til dette, Docker Desktop forbindes.<\/p>\n<p>S\u00e5 for at begynde at arbejde med Docker skal du g\u00f8re to ting:<\/p>\n<ul>\n<li>Installer <noindex><a rel=\"nofollow\" href=\"https:\/\/www.docker.com\/get-started\">Docker Desktop<\/a><\/noindex>.<\/li>\n<li>Tilmeld dig p\u00e5 <noindex><a rel=\"nofollow\" href=\"https:\/\/hub.docker.com\/\">Docker Hub<\/a><\/noindex>.<\/li>\n<\/ul>\n<p>\nDerefter kan du kontrollere, om Docker CLI fungerer ved at k\u00f8re f\u00f8lgende kommando for at tjekke Docker-versionen:<\/p>\n<pre><code class=\"plaintext\">docker -v<\/code><\/pre>\n<p>\nDerefter logger du ind p\u00e5 Docker Hub ved at indtaste dit brugernavn og adgangskode, n\u00e5r du bliver bedt om det:<\/p>\n<pre><code class=\"plaintext\">docker login<\/code><\/pre>\n<p>\nFor at bruge Docker skal du forst\u00e5 begreberne images og containere.<\/p>\n<h3>\u258dImages<\/h3>\n<p>\nEt image er noget, der ligner en plan, der indeholder instruktioner til at bygge en container. Det er et uforanderligt snapshot af filsystemet og applikationens indstillinger. Udviklere kan nemt udveksle images.<\/p>\n<pre><code class=\"plaintext\"># \u0412\u044b\u0432\u043e\u0434 \u0441\u0432\u0435\u0434\u0435\u043d\u0438\u0439 \u043e\u0431\u043e \u0432\u0441\u0435\u0445 \u043e\u0431\u0440\u0430\u0437\u0430\u0445\ndocker images<\/code><\/pre>\n<p>\nDenne kommando vil give en tabel med f\u00f8lgende overskrift:<\/p>\n<pre><code class=\"plaintext\">REPOSITORY \u00a0 \u00a0 TAG     IMAGE ID \u00a0 \u00a0 CREATED     SIZE\n---<\/code><\/pre>\n<p>\nHerefter vil vi se p\u00e5 nogle eksempler p\u00e5 kommandoer i samme format \u2014 f\u00f8rst kommandoen med en kommentar, og derefter et eksempel p\u00e5, hvad den kan producere.<\/p>\n<h3>\u258dContainere<\/h3>\n<p>\nEn container er en eksekverbar pakke, der indeholder alt, hvad der er n\u00f8dvendigt for at k\u00f8re en applikation. Applikationen vil i denne tilgang altid fungere ens, uanset infrastrukturen: i et isoleret milj\u00f8 og i det samme milj\u00f8. Det handler om, at der k\u00f8rer instanser af det samme billede i forskellige milj\u00f8er.<\/p>\n<pre><code class=\"plaintext\"># \u041f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432\ndocker ps -a\nCONTAINER ID \u00a0 \u00a0 IMAGE     COMMAND \u00a0 \u00a0 CREATED     STATUS     PORTS \u00a0 \u00a0 NAMES\n---<\/code><\/pre>\n<p><\/p>\n<h3>\u258dTags<\/h3>\n<p>\nEt tag er en angivelse af en specifik version af et billede.<\/p>\n<h3>\u258dKort vejledning til Docker-kommandoer<\/h3>\n<p>\nHer er et overblik over nogle ofte brugte Docker-kommandoer.<\/p>\n<p>Kommandoen<\/p>\n<p>Kontext<\/p>\n<p>Handling<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/build\/\">docker build<\/a><\/noindex><\/p>\n<p>Billede<\/p>\n<p>Bygning af billede fra Dockerfile<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/tag\/\">docker tag<\/a><\/noindex><\/p>\n<p>Billede<\/p>\n<p>Tagging af billede<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/images\/\">docker images<\/a><\/noindex><\/p>\n<p>Billede<\/p>\n<p>Visning af listen over billeder<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/run\/\">docker run<\/a><\/noindex><\/p>\n<p>Container<\/p>\n<p>Start af container baseret p\u00e5 billede<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/push\/\">docker push<\/a><\/noindex><\/p>\n<p>Billede<\/p>\n<p>Upload af billede til registry<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"http:\/\/about:blank\">docker pull<\/a><\/noindex><\/p>\n<p>Billede<\/p>\n<p>Hentning af billede fra registry<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/ps\/\">docker ps<\/a><\/noindex><\/p>\n<p>Container<\/p>\n<p>Visning af listen over containere<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/system_prune\/\">docker system prune<\/a><\/noindex><\/p>\n<p>Billede\/Container<\/p>\n<p>Fjernelse af ubrugte containere og billeder<\/p>\n<h3>\u258dDockerfile<\/h3>\n<p>\nJeg ved, hvordan jeg lokalt kan k\u00f8re en applikation til produktion. Jeg har en Webpack-konfiguration, der er designet til at bygge et f\u00e6rdigt React-applikation. Derudover har jeg en kommando, der starter en server baseret p\u00e5 Node.js, p\u00e5 port <code>5000<\/code>. Det ser s\u00e5ledes ud:<\/p>\n<pre><code class=\"plaintext\">npm i \u00a0 \u00a0 \u00a0 \u00a0 # installation af afh\u00e6ngigheder\nnpm run build # bygning af React-applikation\nnpm run start # start af Node-server<\/code><\/pre>\n<p>\nDet skal bem\u00e6rkes, at jeg ikke har et eksempelapplikation til dette materiale. Men til eksperimenter kan enhver simpel Node-applikation bruges.<\/p>\n<p>For at bruge containeren skal du give Docker instruktioner. Det g\u00f8res via en fil kaldet <code>Dockerfile<\/code>, som befinder sig i roden af projektmappen. Denne fil virker i starten ret uklar.<\/p>\n<p>Men det, der er i den, beskriver blot, med specielle kommandoer, noget der ligner ops\u00e6tningen af et arbejdsmilj\u00f8. Her er nogle af disse kommandoer:<\/p>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#from\">FROM<\/a><\/noindex> \u2014 Denne kommando starter filen. Den angiver det grundl\u00e6ggende billede, som containeren bygges p\u00e5.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#copy\">COPY<\/a><\/noindex> \u2014 Kopiering af filer fra en lokal kilde til containeren.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#workdir\">WORKDIR<\/a><\/noindex> \u2014 Angivelse af arbejdsbiblioteket for f\u00f8lgende kommandoer.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#run\">RUN<\/a><\/noindex> \u2014 Udf\u00f8relse af kommandoer.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#expose\">EXPOSE<\/a><\/noindex> \u2014 Konfigurering af porten.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/builder\/#entrypoint\">ENTRYPOINT<\/a><\/noindex> \u2014 Angivelse af den kommando, der skal udf\u00f8res.<\/li>\n<\/ul>\n<p>\n<code>Dockerfile<\/code> kan se nogenlunde s\u00e5dan ud:<\/p>\n<pre><code class=\"plaintext\"># \u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043e\u0431\u0440\u0430\u0437\nFROM node:12-alpine\n\n# \u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e app\/\nCOPY . app\/\n\n# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c app\/ \u0432 \u0440\u043e\u043b\u0438 \u0440\u0430\u0431\u043e\u0447\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438\nWORKDIR app\/\n\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (\u043a\u043e\u043c\u0430\u043d\u0434\u0430 npm ci \u043f\u043e\u0445\u043e\u0436\u0430 npm i, \u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0441\u0431\u043e\u0440\u043e\u043a)\nRUN npm ci --only-production\n\n# \u0421\u043e\u0431\u0440\u0430\u0442\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 React-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0430\nRUN npm run build\n\n# \u041f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0440\u0442\nEXPOSE 5000\n\n# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c Node-\u0441\u0435\u0440\u0432\u0435\u0440\nENTRYPOINT npm run start<\/code><\/pre>\n<p>\nAfh\u00e6ngigt af det valgte basisbillede kan det v\u00e6re n\u00f8dvendigt at installere yderligere afh\u00e6ngigheder. Nogle basisbilleder (som Node Alpine Linux) er designet til at v\u00e6re s\u00e5 kompakte som muligt, hvilket betyder, at nogle programmer, du forventer, m\u00e5ske mangler.<\/p>\n<h3>\u258dBygning, taggning og k\u00f8rsel af containeren<\/h3>\n<p>\nLokal bygning og k\u00f8rsel af containeren \u2014 det er, efter vi har <code>Dockerfile<\/code>, opgaverne ret enkle. F\u00f8r du sender billedet til Docker Hub, skal det testes lokalt.<\/p>\n<h3>\u258dBygning<\/h3>\n<p>\nF\u00f8rst skal du bygge <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.docker.com\/engine\/reference\/commandline\/build\/\">billede<\/a><\/noindex>, angiv et navn og, hvis det \u00f8nskes, et tag (hvis tagget ikke angives, vil systemet automatisk tildele billedet et tag <code>latest<\/code>).<\/p>\n<pre><code class=\"plaintext\"># \u0421\u0431\u043e\u0440\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u0430\ndocker build -t &lt;image&gt;:&lt;tag&gt; .<\/code><\/pre>\n<p>\nEfter at have udf\u00f8rt denne kommando kan du se, hvordan Docker bygger billedet.<\/p>\n<pre><code class=\"plaintext\">Sender byggetilstand til Docker daemon \u00a0 2.88MB\nTrin 1\/9 : FRA node:12-alpine\n\u00a0---&gt; ...udf\u00f8rer bygge trin...\nSuccesfuldt bygget 123456789123\nSuccesfuldt tagget &lt;image&gt;:&lt;tag&gt;<\/code><\/pre>\n<p>\nBygningen kan tage et par minutter \u2014 det afh\u00e6nger af, hvor mange afh\u00e6ngigheder du har. Efter bygningen kan du udf\u00f8re kommandoen <code>docker images<\/code> og se beskrivelsen af dit nye billede.<\/p>\n<pre><code class=\"plaintext\">REPOSITORY\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 TAG               IMAGE ID\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 CREATED              SIZE\n&lt;image&gt; \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 latest            123456789123\u00a0 \u00a0 \u00a0 \u00a0 For omtrent et minut siden   x.xxGB<\/code><\/pre>\n<p><\/p>\n<h3>\u258dK\u00f8rsel<\/h3>\n<p>\nBilledet er oprettet. Det betyder, at du kan starte en container baseret p\u00e5 det. Da jeg \u00f8nsker, at jeg skal kunne f\u00e5 adgang til applikationen, der k\u00f8rer i containeren, p\u00e5 adressen <code>localhost:5000<\/code>, har jeg i venstre del af paragrafen <code>5000:5000<\/code> i den n\u00e6ste kommando angivet <code>5000<\/code>. I den h\u00f8jre del er der containerens port.<\/p>\n<pre><code class=\"plaintext\"># \u0417\u0430\u043f\u0443\u0441\u043a \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u0440\u0442\u0430 5000 \u0438 \u043f\u043e\u0440\u0442\u0430 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 5000\ndocker run -p 5000:5000 &lt;image&gt;:&lt;tag&gt;<\/code><\/pre>\n<p>\nNu, hvor containeren er oprettet og k\u00f8rt, kan du bruge kommandoen <code>docker ps<\/code> for at se oplysninger om denne container (eller du kan bruge kommandoen <code>docker ps -a<\/code>, som viser oplysninger om alle containere, ikke kun de k\u00f8rende).<\/p>\n<pre><code class=\"plaintext\">CONTAINER ID        IMAGE               COMMAND                    CREATED              STATUS                   PORTS                    NAMES\n987654321234                     \"\/bin\/sh -c 'npm run\u2026\"    6 sekunder siden        K\u00f8rer i 6 sekunder        0.0.0.0:5000-&gt;5000\/tcp   stoic_darwin<\/code><\/pre>\n<p>\nHvis du nu g\u00e5r til adressen <code>localhost:5000<\/code> \u2014 kan du se siden af den k\u00f8rende applikation, som ser n\u00f8jagtig ud som siden af applikationen, der k\u00f8rer i produktionsmilj\u00f8et.<\/p>\n<h3>\u258dTildeling af tag og publicering<\/h3>\n<p>\nFor to take advantage of one of the created images on the production server, we need to ensure that we have the ability to upload this image from Docker Hub. This means we first need to create a repository for the project on Docker Hub. After that, we will have a place where we can send the image. The image needs to be renamed so that its name begins with our username on Docker Hub. Following that, the repository name should follow. At the end of the name, any tag can be placed. Below is an example of naming images according to this scheme.<\/p>\n<p>Now let's build the image with a new name assigned to it and execute the command <code>docker push<\/code> to send it to the Docker Hub repository.<\/p>\n<pre><code class=\"plaintext\">docker build -t \\\/ : .\ndocker tag \\\/ : \\\/ :latest\ndocker push \\\/ :\n\n# In practice, this might look, for example, like this:\ndocker build -t user\/app:v1.0.0 .\ndocker tag user\/app:v1.0.0 user\/app:latest\ndocker push user\/app:v1.0.0<\/code><\/pre>\n<p>\nIf everything goes well, the image will be available on Docker Hub and can be easily uploaded to the server or shared with other developers.<\/p>\n<h2>N\u00e6ste skridt<\/h2>\n<p>\nBy now, we have confirmed that the application, in the form of a Docker container, works locally. We have uploaded the container to Docker Hub. All of this means that we have made significant progress towards our goal. Now we need to resolve two more questions:<\/p>\n<ul>\n<li>Setting up the CI tool for testing and deploying the code.<\/li>\n<li>Setting up the production server so it can upload and run our code.<\/li>\n<\/ul>\n<p>\nIn our case, the CI\/CD solution used is <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">Travis CI<\/a><\/noindex>. As a server \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.digitalocean.com\/\">DigitalOcean<\/a><\/noindex>.<\/p>\n<p>It should be noted that other combinations of services can also be utilized here. For example, instead of Travis CI, one could use CircleCI or GitHub Actions. And instead of DigitalOcean \u2014 AWS or Linode.<\/p>\n<p>We decided to work with Travis CI, and I already have some setup configured in this service. Therefore, I will briefly explain how to prepare it for use.<\/p>\n<h2>Travis CI<\/h2>\n<p>\nTravis CI is a tool for testing and deploying code. I wouldn't want to go into the details of configuring Travis CI, as each project is unique, and this wouldn\u2019t be particularly useful. But I will explain the basics that will allow you to get started if you decide to use Travis CI. Whatever you choose \u2014 Travis CI, CircleCI, Jenkins, or something else, similar configuration methods will apply.<\/p>\n<p>For at komme i gang med Travis CI, skal du g\u00e5 til <noindex><a rel=\"nofollow\" href=\"https:\/\/travis-ci.org\/\">projektets hjemmeside<\/a><\/noindex> og oprette en konto. Derefter skal du integrere Travis CI med din GitHub-konto. Under ops\u00e6tningen af systemet vil du skulle angive det repository, som du vil automatisere arbejdet med, og give adgang til det. (Jeg bruger GitHub, men jeg er sikker p\u00e5, at Travis CI ogs\u00e5 kan integreres med BitBucket, GitLab og andre lignende tjenester).<\/p>\n<p>Hver gang Travis CI s\u00e6ttes i gang, starter en server, der udf\u00f8rer de angivne kommandoer i konfigurationsfilen, inklusive udrulning af de relevante grene i repository'et.<\/p>\n<h3>\u258dJoblivscyklus<\/h3>\n<p>\nKonfigurationsfilen for Travis CI, som kaldes <code>.travis.yml<\/code> og som opbevares i rodmappen af projektet, underst\u00f8tter konceptet for begivenheder <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.travis-ci.com\/user\/job-lifecycle\/\">joblivscyklus<\/a><\/noindex> for job. Her er disse begivenheder, pr\u00e6senteret i den r\u00e6kkef\u00f8lge, de opst\u00e5r:<\/p>\n<ul>\n<li><code>apt addons<\/code><\/li>\n<li><code>cache components<\/code><\/li>\n<li><code>before_install<\/code><\/li>\n<li><code>installation<\/code><\/li>\n<li><code>before_script<\/code><\/li>\n<li><code>script<\/code><\/li>\n<li><code>before_cache<\/code><\/li>\n<li><code>after_success eller after_failure<\/code><\/li>\n<li><code>before_deploy<\/code><\/li>\n<li><code>deploy<\/code><\/li>\n<li><code>after_deploy<\/code><\/li>\n<li><code>after_script<\/code><\/li>\n<\/ul>\n<p><\/p>\n<h3>\u258dTestning<\/h3>\n<p>\nI konfigurationsfilen vil jeg ops\u00e6tte en lokal Travis CI-server. Jeg har valgt Node version 12 som sprog og angivet til systemet, at det skal installere de afh\u00e6ngigheder, der er n\u00f8dvendige for at bruge Docker.<\/p>\n<p>Alt, hvad der er angivet i <code>.travis.yml<\/code>, vil blive udf\u00f8rt ved alle pull-anmodninger til alle grene i repository'et, medmindre andet er angivet. Dette er en nyttig funktion, da det betyder, at vi kan teste al koden, der kommer ind i repository'et. Dette sikrer, at vi ved, om koden er klar til at blive skrevet i grenen <code>master<\/code>, og om den vil forstyrre byggeprocessen for projektet. I denne globale konfiguration installerer jeg alt lokalt, k\u00f8rer Webpack udvikler-serveren i baggrunden (dette er en funktion i min arbejdsproces) og udf\u00f8rer testene.<\/p>\n<p>Hvis du \u00f8nsker, at dit repository skal vise badges med information om test d\u00e6kning, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.taniarascia.com\/display-build-status-and-test-coverage\/\">her<\/a><\/noindex> kan du finde en kort vejledning til brug af Jest, Travis CI og Coveralls til at opsamle og vise disse oplysninger.<\/p>\n<p>S\u00e5 her er indholdet af filen <code>.travis.yml<\/code>:<\/p>\n<pre><code class=\"plaintext\"># \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u044f\u0437\u044b\u043a\nlanguage: node_js\n\n# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044e Node.js\nnode_js:\n\u00a0\u00a0- '12'\n\nservices:\n\u00a0\u00a0# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 Docker\n\u00a0\u00a0- docker\n\ninstall:\n\u00a0\u00a0# \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\n\u00a0\u00a0- npm ci\n\nbefore_script:\n\u00a0\u00a0# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432\n\u00a0\u00a0- npm run dev &amp;\n\nscript:\n\u00a0\u00a0# \u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u044b\n\u00a0\u00a0- npm run test<\/code><\/pre>\n<p>\nHer slutter de handlinger, der udf\u00f8res for alle grene i repository'et og for pull-anmodninger.<\/p>\n<h3>\u258dUdrulning<\/h3>\n<p>\nGivet antagelsen om, at alle automatiserede tests er blevet gennemf\u00f8rt med succes, kan vi, hvilket ikke er n\u00f8dvendigt, udrulle koden p\u00e5 produktionsserveren. Da vi kun \u00f8nsker at g\u00f8re dette for koden fra grenen <code>master<\/code>, vi giver systemet de relevante instruktioner i deploymentsindstillingerne. F\u00f8r du pr\u00f8ver at bruge koden, som vi vil gennemg\u00e5, i dit projekt, vil jeg gerne advare dig om, at du skal have et reelt script, der kaldes for deployment.<\/p>\n<pre><code class=\"plaintext\">deploy:\n  # Byg Docker-container og send den til Docker Hub\n  provider: script\n  script: bash deploy.sh\n  on:\n    branch: master<\/code><\/pre>\n<p>\nDeployment-scriptet l\u00f8ser to opgaver:<\/p>\n<ul>\n<li>Bygning, tagging og upload af billedet til Docker Hub ved hj\u00e6lp af CI-v\u00e6rkt\u00f8jet (i vores tilf\u00e6lde er det Travis CI).<\/li>\n<li>Upload af billedet til serveren, stop af den gamle container og start af den nye (i vores tilf\u00e6lde k\u00f8rer serveren p\u00e5 platformen DigitalOcean).<\/li>\n<\/ul>\n<p>\nF\u00f8rst skal automatiseringsprocessen til bygning, tagging og upload af billedet til Docker Hub indstilles. Alt dette ligner meget det, vi tidligere har gjort manuelt, bortset fra at vi her har brug for en strategi for at tildele billederne unikke tags og automatisere login. Jeg havde nogle udfordringer med visse detaljer i deployment-scriptet, s\u00e5som tagging-strategien, login, SSH-n\u00f8glekodning og etablering af SSH-forbindelsen. Men heldigvis h\u00e5ndterer min k\u00e6reste bash meget godt, ligesom mange andre ting. Han hjalp mig med at skrive dette script.<\/p>\n<p>S\u00e5 den f\u00f8rste del af scriptet er at sende billedet til Docker Hub. Det er ret enkelt. Den tagging-skema, jeg brugte, indeb\u00e6rer at kombinere git-hash med git-tag, hvis det eksisterer. Dette sikrer, at der oprettes et unikt tag og forenkler identificeringen af den build, som det er baseret p\u00e5. <code>DOCKER_USERNAME<\/code> og <code>DOCKER_PASSWORD<\/code> \u2014 det er brugerdefinerede milj\u00f8variabler, der kan angives via gr\u00e6nsefladen i Travis CI. Travis CI vil automatisk h\u00e5ndtere hemmelige oplysninger, s\u00e5 de ikke falder i forkert h\u00e6nder.<\/p>\n<p>Her er den f\u00f8rste del af scriptet <code>deploy.sh<\/code>.<\/p>\n<pre><code class=\"plaintext\">#!\/bin\/sh\nset -e # \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043e\u0448\u0438\u0431\u043e\u043a\n\nIMAGE=&quot;&lt;username&gt;\/&lt;repository&gt;&quot; \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 # \u041e\u0431\u0440\u0430\u0437 Docker\nGIT_VERSION=$(git describe --always --abbrev --tags --long) # Git-\u0445\u044d\u0448 \u0438 \u0442\u0435\u0433\u0438\n\n# \u0421\u0431\u043e\u0440\u043a\u0430 \u0438 \u0442\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0431\u0440\u0430\u0437\u0430\ndocker build -t ${IMAGE}:${GIT_VERSION} .\ndocker tag ${IMAGE}:${GIT_VERSION} ${IMAGE}:latest\n\n# \u0412\u0445\u043e\u0434 \u0432 Docker Hub \u0438 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0430 \u043e\u0431\u0440\u0430\u0437\u0430\necho &quot;${DOCKER_PASSWORD}&quot; | docker login -u &quot;${DOCKER_USERNAME}&quot; --password-stdin\ndocker push ${IMAGE}:${GIT_VERSION}<\/code><\/pre>\n<p>\nHvordan den anden del af scriptet ser ud, afh\u00e6nger helt af, hvilken hostingudbyder du bruger, og hvordan forbindelsen er organiseret. I mit tilf\u00e6lde, da jeg bruger Digital Ocean, anvendes kommandoerne <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/digitalocean\/doctl\">doctl<\/a><\/noindex>. Ved arbejde med AWS vil v\u00e6rkt\u00f8jet <code>aws<\/code>, og s\u00e5 videre.<\/p>\n<p>Det var ikke s\u00e6rlig sv\u00e6rt at konfigurere serverens funktion. Jeg opsatte en dr\u00e5be baseret p\u00e5 det grundl\u00e6ggende billede. Det skal bem\u00e6rkes, at det valgte system kr\u00e6ver \u00e9n manuel installation af Docker og \u00e9n manuel opstart af Docker. Jeg brugte Ubuntu 18.04 til installationen af Docker, s\u00e5 hvis du ogs\u00e5 bruger Ubuntu, kan du bare f\u00f8lge <noindex><a rel=\"nofollow\" href=\"https:\/\/phoenixnap.com\/kb\/how-to-install-docker-on-ubuntu-18-04\">denne<\/a><\/noindex> den enkle vejledning.<\/p>\n<p>Jeg taler ikke om specifikke kommandoer til tjenesten, da dette aspekt kan variere meget i forskellige tilf\u00e6lde. Jeg vil blot give en overordnet handlingsplan, der udf\u00f8res efter at have tilsluttet sig serveren via SSH, hvor projektet skal implementeres:<\/p>\n<ul>\n<li>Du skal finde den container, der k\u00f8rer i \u00f8jeblikket, og stoppe den.<\/li>\n<li>Derefter skal du i baggrunden starte en ny container.<\/li>\n<li>Du skal indstille den lokale servers port til <code>80<\/code> \u2014 dette vil g\u00f8re det muligt at tilg\u00e5 webstedet p\u00e5 en adresse som <code>example.com<\/code>, uden at angive porten, i stedet for at bruge en adresse som <code>example.com:5000<\/code>.<\/li>\n<li>Og endelig skal du slette alle gamle containere og billeder.<\/li>\n<\/ul>\n<p>\nHer er forts\u00e6ttelsen af scriptet.<\/p>\n<pre><code class=\"plaintext\"># \u041d\u0430\u0439\u0442\u0438 ID \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430\nCONTAINER_ID=$(docker ps | grep takenote | cut -d&quot; &quot; -f1)\n\n# \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440, \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439, \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443\ndocker stop ${CONTAINER_ID}\ndocker run --restart unless-stopped -d -p 80:5000 ${IMAGE}:${GIT_VERSION}\ndocker system prune -a -f<\/code><\/pre>\n<p><\/p>\n<h2>Nogle ting, du skal v\u00e6re opm\u00e6rksom p\u00e5<\/h2>\n<p>\nM\u00e5ske n\u00e5r du tilslutter til serveren via SSH fra Travis CI, vil du se en advarsel, der forhindrer dig i at forts\u00e6tte installationen, da systemet venter p\u00e5 brugerens reaktion.<\/p>\n<pre><code class=\"plaintext\">Autenticiteten af v\u00e6rten '&lt;hostname&gt; (&lt;IP address&gt;)' kan ikke fastsl\u00e5s.\nRSA n\u00f8glefingeraftryk er &lt;key fingerprint&gt;.\nEr du sikker p\u00e5, at du vil forts\u00e6tte med at tilslutte (ja\/nej)?<\/code><\/pre>\n<p>\nJeg har fundet ud af, at strengn\u00f8glen kan kodes i base64 for at gemme den i en form, der er nem at arbejde med. Under installationen kan den offentlige n\u00f8gle dekodes og skrives til filen <code>known_hosts<\/code> for at eliminere den f\u00f8rn\u00e6vnte fejl.<\/p>\n<pre><code class=\"plaintext\">echo &lt;public key&gt; | base64 # udskriver &lt;den offentlige n\u00f8gle, kodet i base64&gt;<\/code><\/pre>\n<p>\nI praksis kan denne kommando se s\u00e5ledes ud:<\/p>\n<pre><code class=\"plaintext\">echo \"123.45.67.89 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom\/BWDSU\nGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3\nPbv7kOdJ\/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK\/7XA\nt3FaoJoAsncM1Q9x5+3V0Ww68\/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw\/Pb0rwert\/En\nmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z\/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx\nNrRFi9wrf+M7Q== you@example.com\" | base64<\/code><\/pre>\n<p>\nOg her er, hvordan det ser ud \u2014 en streng i base64-kodning:<\/p>\n<pre><code class=\"plaintext\">123.45.67.89 ssh-rsa AAAAB3NzaC1yc2EAAAAABIwAB...<\/code><\/pre>\n<p>\nHer er kommandoen, der blev n\u00e6vnt tidligere<\/p>\n<pre><code class=\"plaintext\">install:\n  - echo  | base64 -d &gt;&gt; $HOME\/.ssh\/known_hosts<\/code><\/pre>\n<p>\nDen samme tilgang kan anvendes med den private n\u00f8gle, n\u00e5r der oprettes forbindelse, da du muligvis har brug for den private n\u00f8gle for at f\u00e5 adgang til serveren. N\u00e5r du arbejder med n\u00f8glen skal du blot s\u00f8rge for at opbevare den sikkert i en Travis CI-milj\u00f8variabel, s\u00e5 den ikke bliver vist.<\/p>\n<p>En anden ting at v\u00e6re opm\u00e6rksom p\u00e5 er, at du m\u00e5ske skal k\u00f8re hele deployeringsscriptet som en enkelt linje, for eksempel \u2014 ved hj\u00e6lp af <code>doctl<\/code>. Det kan kr\u00e6ve lidt ekstra arbejde.<\/p>\n<pre><code class=\"plaintext\">doctl compute ssh  --ssh-command \"alle kommandoer vil v\u00e6re her &amp;&amp; her\"<\/code><\/pre>\n<p><\/p>\n<h2>TLS\/SSL og lastbalancering<\/h2>\n<p>\nEfter at jeg har gjort alt det, der er n\u00e6vnt tidligere, var det sidste problem, jeg st\u00f8dte p\u00e5, at serveren ikke havde SSL. Da jeg bruger en Node.js-server, for at f\u00e5 den til at arbejde <noindex><a rel=\"nofollow\" href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/how-to-secure-a-containerized-node-js-application-with-nginx-let-s-encrypt-and-docker-compose\">arbejde<\/a><\/noindex> omvendt proxy Nginx og Let\u2019s Encrypt, kr\u00e6ver det en del arbejde.<\/p>\n<p>Jeg \u00f8nskede slet ikke at konfigurere alle disse SSL-indstillinger manuelt, s\u00e5 jeg oprettede bare en lastbalancer og registrerede oplysningerne om den i DNS. I tilf\u00e6lde af DigitalOcean er oprettelsen af et selvudf\u00f8rende, selvsigneret certifikat p\u00e5 lastbalancen en simpel, gratis og hurtig procedure. En s\u00e5dan tilgang har ogs\u00e5 den ekstra fordel, at det, om n\u00f8dvendigt, g\u00f8r det meget let at konfigurere SSL p\u00e5 flere servere, der k\u00f8rer bag lastbalancen. Dette betyder, at serverne ikke beh\u00f8ver at \"t\u00e6nke\" over SSL, men stadig kan bruge port <code>80<\/code>. S\u00e5 konfiguration af SSL p\u00e5 lastbalanceren er langt lettere og mere bekvemt end alternative metoder til at konfigurere SSL.<\/p>\n<p>Nu kan du lukke alle porte p\u00e5 serveren, der accepterer indg\u00e5ende forbindelser \u2014 undtagen port <code>80<\/code>, der bruges til kommunikation med lastbalanceren. <code>22<\/code> for SSH. As a result, any attempt to directly access the server on any ports, except for these two, will fail.<\/p>\n<h2>Resum\u00e9<\/h2>\n<p>\nAfter I did everything I described in this material, I was no longer intimidated by the Docker platform or the concepts of automated CI\/CD pipelines. I managed to set up a continuous integration chain, during which the code is tested before it goes into production, and the code is automatically deployed on the server. All of this is still relatively new to me, and I am sure there are ways to improve my automated workflow and make it more effective. So if you have any ideas on this, please let <noindex><a rel=\"nofollow\" href=\"mailto:hello@taniarascia.com\">me<\/a><\/noindex> know. I hope this article has helped you in your endeavors. I like to think that after reading it, you learned as much as I did while figuring out everything I wrote about.<\/p>\n<p><b>P.S.<\/b> I vores <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/marketplace#order\">marketplace<\/a><\/noindex> there is an image <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/marketplace\/docker#advantages\">Docker<\/a><\/noindex>, which can be installed with one click. You can check the work of the containers on <noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/\">VPS<\/a><\/noindex>. All new clients are given 3 days for free testing.<\/p>\n<p><b>K\u00e6re l\u00e6sere!<\/b> Do you use CI\/CD technologies in your projects?<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/ruvds.com\/ru-rub\/#order\"><img decoding=\"async\" alt=\"Oprettelse af CI\/CD-k\u00e6de og automatisering af arbejde med Docker\" src=\"\/wp-content\/uploads\/2020\/02\/3fbfa90ea56a3d9b6b89ba945eb94570.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex><br \/>\n<br \/>Kilde: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/ruvds\/blog\/488668\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e. \u0411\u044b\u043b Apache-\u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0430 \u043a\u0430\u043a\u043e\u043c-\u043d\u0438\u0431\u0443\u0434\u044c \u043e\u0431\u0449\u0435\u043c \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0435, \u043d\u0430 \u044d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0432\u043e\u0439\u0442\u0438 \u043f\u043e FTP, \u043d\u0430\u043f\u0438\u0441\u0430\u0432 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u043d\u0435\u0447\u0442\u043e \u0432\u0440\u043e\u0434\u0435 ftp:\/\/ftp.example.com. \u041f\u043e\u0442\u043e\u043c \u043d\u0430\u0434\u043e \u0431\u044b\u043b\u043e \u0432\u0432\u0435\u0441\u0442\u0438 \u0438\u043c\u044f \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440. \u0414\u0440\u0443\u0433\u0438\u0435 \u0431\u044b\u043b\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0430, \u0432\u0441\u0451 \u0442\u043e\u0433\u0434\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":69352,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-69351","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.3 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"da_DK\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 CI\/CD-\u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-02-18T11:44:06+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-03T13:15:00+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Creating a CI\/CD chain and automating work with Docker | ProHoster","description":"I wrote my first websites in the late 90s. At that time, it was very easy to get them working.","canonical_url":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"da_DK","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 CI\/CD-\u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Docker | ProHoster","og:description":"\u042f \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0430 \u043c\u043e\u0438 \u043f\u0435\u0440\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b \u0432 \u043a\u043e\u043d\u0446\u0435 90-\u0445. \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u0438\u0445 \u0432 \u0440\u0430\u0431\u043e\u0447\u0435\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0431\u044b\u043b\u043e \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e.","og:url":"https:\/\/prohoster.info\/da\/blog\/administrirovanie\/sozdanie-ci-cd-czepochki-i-avtomatizacziya-raboty-s-docker","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-02-18T11:44:06+00:00","article:modified_time":"2020-03-03T13:15:00+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"69351","title":null,"description":null,"keywords":null,"keyphrases":{"focus":[],"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 19:24:45","updated":"2026-08-11 12:50:24","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts\/69351","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/comments?post=69351"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/posts\/69351\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media\/69352"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/media?parent=69351"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/categories?post=69351"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/da\/wp-json\/wp\/v2\/tags?post=69351"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}