One place for hosting & domains

      Application

      How To Package and Publish a Snap Application on Ubuntu 18.04


      The author selected the Electronic Frontier Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      One of the largest challenges in application development is the final step of distributing the finished product to your users or customers. Many existing application deployment methodologies lack user-friendliness and security, or do not provide methods for automatically updating an application once it has been installed.

      Snap is a modern application packaging format with powerful sandboxing and security features, including file system isolation, automatic updates, and integrated dependency management. Snap applications, known as Snaps, can be downloaded and installed using a command-line program, much like apt or yum. Ubuntu comes with Snap pre-installed, meaning that there is a wide audience for Snap applications.

      In this tutorial, you will create a Snap application and publish it on the Snap Store.

      Prerequisites

      To complete this tutorial, you will need:

      • One Ubuntu 18.04 server set up by following the Initial Server Setup with Ubuntu 18.04, including a sudo non-root user.

      • An application that you wish to package and release as a Snap. This may be a complex application that you created, a common open-source project, or a simple “Hello, world!” program. If you don’t already have an application, Step 1 of this tutorial will cover how you can create a Hello World program in Go.

      • An account on the Snapcraft Developer Dashboard.

      Once you have these ready, log in to your server as your non-root user to begin.

      Step 1 — Getting Your Application Ready for Packaging

      Firstly, you’ll prepare your application for packaging as a Snap application by ensuring that everything required is present in a single directory.

      Start by creating a new directory for your Snap and moving into it:

      • mkdir ~/your-snap
      • cd ~/your-snap

      Next, if you already have an application, put a complete copy of the source code for your application into the directory that you just created. The process here will vary significantly depending on the exact application that you’re packaging, however in the case that the source code is stored in a Git repository, you can git init a repository in the directory and pull down all of the relevant code.

      If you don’t yet have an application that you’d like to package, you may create a “Hello World” program to use instead. If you would like more context on writing this program with Go, check out the How to Write Your First Program in Go tutorial.

      You can do this by first creating a new Go file and opening it using your preferred text editor:

      Next, add the following code to the file:

      helloworld.go

      package main
      import "fmt"
      func main() {
        fmt.Println("Hello, world!")
      }
      

      Then save and exit the file.

      If you don’t have Go installed, you can install it using the following command:

      • sudo apt install golang-go

      Once Go is installed, you can run your new program to check that it is working:

      You’ll see the following output:

      Output

      Hello, world!

      You’ve prepared your application for packaging as a Snap. Next, you will install the software required to begin the packaging process.

      Step 2 — Installing Snapcraft

      In this step, you’ll download and install Snapcraft, which is the name of the official Snap application packaging tool. Snapcraft is available from the Snap Store, which is built into Ubuntu by default. This means that you can install Snapcraft from the command-line using the snap command.

      The snap command is equivalent to the apt command, but you can use it to install software from the Snap Store, rather than packages from the Apt repositories.

      In order to install Snapcraft, run the following command:

      • sudo snap install snapcraft --classic

      You use the --classic command argument so that Snapcraft installs without the strict sandboxing features that Snaps normally use. Snapcraft requires this argument as it needs more privileged access to your system to reliably package applications.

      Once you’ve installed Snapcraft, you’ll see the following:

      Output

      snapcraft 3.9.8 from Canonical✓ installed

      Finally, you can double-check the Snapcraft installation by running:

      This will display something similar to:

      Output

      snapcraft, version 3.9.8

      Now that you’ve installed Snapcraft, you can begin to define the configuration and metadata for your Snap application.

      In this step, you will begin to define the configuration, structure, and metadata for your Snap application.

      Begin by ensuring that you are still working in your Snap application directory:

      Next, create and edit the snapcraft.yaml file using your preferred text editor:

      You’ll use the snapcraft.yaml file to store all of the configuration for your Snap application, including the name, description, and version, as well as settings related to dependency management and sandboxing.

      Begin by defining the name, summary, description, and version number for your application:

      snapcraft.yaml

      name: your-snap
      summary: A summary of your application in 78 characters or less.
      description: |
        A detailed description of your application.
        The description can have multiple lines.
      version: '1.0'
      

      The name of your Snap needs to be unique if you wish to publish it on the Snap Store—search for other applications with the same name to make sure that it isn’t already taken.

      Next, you can define the command(s) that you wish to associate with your application. This will allow your Snap to be used directly from the Bash command-line as a normal command.

      Add the following to your snapcraft.yaml file:

      snapcraft.yaml

      . . .
      apps:
        your-snap-command:
          command: your-snap
      

      your-snap-command is the name of the command that you want to define. For example, you may wish to use the command helloworld to run your Hello World program.

      You use command: your-snap to tell Snapcraft what to do when the application command is run. In the case of the Hello World program, you would use the value helloworld to reference the helloworld.go file, which will allow Snapcraft to run your program successfully.

      This results in the following example configuration:

      snapcraft.yaml

      apps:
        helloworld:
          command: helloworld
      

      If the command name matches the Snap name exactly, you’ll be able to run it directly from the command-line. If the command does not match the Snap name, the command will be automatically prefixed with the name of the Snap. For example, helloworld.command1.

      Finally, you can define the parts that make up your Snap application. Snap applications are made up of multiple parts, which are all of the components that make up your application. In many cases, there is only one part, which is the application itself.

      Each part has an associated plugin. For example, for components of your application written in Ruby, the ruby plugin is used, and for components written in Go, the go plugin is used.

      You can use the Snapcraft list-plugins command to identify the correct plugin(s) for your application:

      This will output a list similar to the following:

      Output

      ant catkin-tools conda dump gradle make nil python rust autotools cmake crystal go kbuild maven nodejs qmake scons catkin colcon dotnet godeps kernel meson plainbox-provider ruby waf

      The most common plugins are those for common programming languages, such as Go, Rust, Ruby, or Python.

      Once you have identified the correct plugins for your application, you can begin to add the parts configuration to your snapcraft.yaml file:

      snapcraft.yaml

      . . .
      parts:
        your-snap:
          plugin: plugin-name
          source: .
      

      You use the source configuration parameter to specify the relative path to the source code for your application. Usually this will be the same directory as the snapcraft.yaml file itself, so the source value is a single dot (.).

      Note: If your application component has any dependencies that are required for either building or running it, you can specify these using the build-packages and stage-packages attributes. The specified dependency names will then be automatically fetched from the default package manager for your system.

      For example:

      snapcraft.yaml

      parts:
        your-snap:
        plugin: plugin-name
        source: .
        build-packages:
        - gcc
        - make
        stage-packages:
        - libcurl4
      

      Some Snapcraft plugins have their own specific options that may be required for your application, so it’s worthwhile reviewing the relevant manual pages for your plugin:

      • snapcraft help plugin-name

      In the case of Go applications, you would also specify the go-importpath. For the Hello World configuration, this results in the following example configuration:

      snapcraft.yaml

      parts:
        helloworld:
          plugin: go
          source: .
          go-importpath: helloworld
      

      You can leave your snapcraft.yaml file open to add further configuration in the next step.

      You’ve defined the base configuration for your Snap application. Next, you’ll configure the security and sandboxing aspects of your application.

      Step 4 — Securing Your Snap Application

      Snap applications are designed to run within a sandboxed environment, so in this step you’ll configure sandboxing for your Snap. Firstly, you’ll need to enable sandboxing for your application, known within Snapcraft as confinement.

      Add the following to your snapcraft.yaml file:

      snapcraft.yaml

      . . .
      confinement: strict
      

      This will enable sandboxing for your application, preventing it from accessing the internet, other running Snaps, or the host system itself. However, in most cases, applications do need to be able to communicate outside of their sandbox like when they need to access the internet or read/write to the file system.

      These permissions, known within Snapcraft as interfaces, can be granted to your Snap application using Plugs. Using Plugs, you can have fine-grain control over the sandboxing for your application, to give it the access that it requires and nothing more (principle of least privilege).

      The exact interfaces that are required will vary depending on your application. Some of the most common interfaces are:

      • audio-playback - Allows audio output/playing sounds.
      • audio-record - Allows audio input/recording.
      • camera - Allows access to connected webcams.
      • home - Allows access to non-hidden files within your home directory.
      • network - Allows access to the network/internet.
      • network-bind - Allows binding to ports to operate as a network service.
      • system-files - Allows access to the entire file system of the host machine.

      The full list of available interfaces can be found within the Snapcraft documentation under Supported Interfaces.

      Once you’ve identified all of the required interfaces for your application, you can begin to assign these to plugs within your snapcraft.yaml file.

      The following example configuration will allow the application to access the network and users’ home area:

      snapcraft.yaml

      . . .
      plugs:
        your-snap-home:
          interface: home
        your-snap-network:
          interface: network
      

      Save and exit your file.

      The name of the Plug should be a descriptive name to help users identify the purpose of the Plug.

      You’ve enabled sandboxing for your Snap and configured some Plugs to grant limited access to system resources. Next you’ll finish building your Snap app.

      Step 5 — Building and Testing Your Snap Application

      Now that you’ve written all of the required configuration for your Snap, you can proceed with building it and testing the Snap package locally.

      If you’ve been following along using a Hello World program as your application, your complete snapcraft.yaml file will now look similar to the following:

      snapcraft.yaml

      name: helloworld
      summary: A simple Hello World program.
      description: |
        A simple Hello World program written in Go.
        Packaged as a Snap application using Snapcraft.
      version: '1.0'
      confinement: strict
      
      apps:
        helloworld:
          command: helloworld
      
      parts:
        helloworld:
          plugin: go
          source: .
          go-importpath: helloworld
      
      plugs:
        helloworld-home:
          interface: home
        helloworld-network:
          interface: network
      

      In order to build your Snap application, run the snapcraft command from within the directory for your Snap:

      Snapcraft will then automatically launch a virtual machine (VM) and begin building your Snap. Once complete, Snapcraft will exit and you will see something similar to the following:

      Output

      Snapped your-snap_1.0_amd64.snap

      You can now install your Snap locally in order to check that it is working:

      • sudo snap install your-snap.snap --dangerous

      The --dangerous command argument is required as you are installing a local Snap that has not been signed.

      Output

      your-snap 1.0 installed

      Once the installation process is complete, you can then run your Snap using its associated command. For example:

      In the case of the example Hello World program, the following output would be:

      Output

      Hello, world!

      You can also view the sandboxing policy for your Snap to make sure that the assigned permissions have been properly granted:

      • snap connections your-snap

      This will output a list of Plugs and interfaces, similar to the following:

      Output

      snap connections your-snap Interface Plug Slot Notes home your-snap:your-snap-home :home - network your-snap:your-snap-network :network -

      In this step, you built your Snap and installed it locally to test that it is working. Next, you’ll publish your Snap on the Snap Store.

      Step 6 — Publishing Your Snap

      Now that you’ve built and tested your Snap application, it’s time to release it on the Snap Store.

      Begin by logging in to your Snap Developer account using the Snapcraft command-line application:

      Follow the prompts to enter your email address and password.

      Next, you need to register the name of the application on the Snap Store:

      • snapcraft register your-snap

      Once you’ve registered the Snap name, you can push the built Snap package to the store:

      • snapcraft push your-snap.snap

      You’ll see output similar to the following:

      Output

      Preparing to push 'your-snap_1.0_amd64.snap'. Install the review-tools from the Snap Store for enhanced checks before uploading this snap. Pushing 'your-snap_1.0_amd64.snap' [===================================================================================================] 100% Processing...| Ready to release! Revision 1 of 'your-snap' created.

      Each time you push to the Snap store, the revision number is incremented, starting at one. This is useful to help identify the various different builds of your Snap.

      Finally, you can release your Snap to the public:

      • snapcraft release your-snap revision-number channel

      If this is the first time you’ve pushed to the Snap Store, the revision number will be 1. You can also choose between releasing to the stable, candidate, beta, and edge channels, if you have multiple versions of your application at different stages of development.

      For example, the following command will release revision 1 of the Hello World Snap to the stable channel:

      • snapcraft release helloworld 1 stable

      You’ll see output similar to the following:

      Output

      Track Arch Channel Version Revision latest amd64 stable 1.0 1 candidate ^ ^ beta ^ ^ edge ^ ^ The 'stable' channel is now open.

      You can now search for your application on the Snap Store and install it on any of your devices.

      Snapcraft store with HelloWorld App displayed from search results

      In this final step, you uploaded your built Snap package to the Snap Store and released it to the public.

      Conclusion

      In this article you configured and built a Snap application, and then released it to the public via the Snap Store. You now have the foundational knowledge required to maintain your application and building new ones.

      If you wish to explore Snaps further, you may wish to browse the full Snap Store. You may also wish to review the Snapcraft YAML Reference to understand more about it and identify additional attributes for your Snap configuration.

      Finally, if you’d like to investigate Snap development further, you may enjoy reading about and implementing Snap Hooks, which allow Snaps to dynamically react to system changes such as upgrades or security policy adjustments.



      Source link

      How To Build a Node.js Application with Docker [Quickstart]


      Introduction

      This tutorial will walk you through creating an application image for a static website that uses the Express framework and Bootstrap. You will then build a container using that image, push it to Docker Hub, and use it to build another container, demonstrating how you can recreate and scale your application.

      For a more detailed version of this tutorial, with more detailed explanations of each step, please refer to How To Build a Node.js Application with Docker.

      Prerequisites

      To follow this tutorial, you will need:

      • A sudo user on your server or in your local environment.
      • Docker.
      • Node.js and npm.
      • A Docker Hub account.

      Step 1 — Installing Your Application Dependencies

      First, create a directory for your project in your non-root user’s home directory:

      Navigate to this directory:

      This will be the root directory of the project.

      Next, create a package.json with your project’s dependencies:

      Add the following information about the project to the file; be sure to replace the author information with your own name and contact details:

      ~/node_project/package.json

      {
        "name": "nodejs-image-demo",
        "version": "1.0.0",
        "description": "nodejs image demo",
        "author": "Sammy the Shark <sammy@example.com>",
        "license": "MIT",
        "main": "app.js",
        "scripts": {
          "start": "node app.js",
          "test": "echo "Error: no test specified" && exit 1"
        },
        "keywords": [
          "nodejs",
          "bootstrap",
          "express"
        ],
        "dependencies": {
          "express": "^4.16.4"
        }
      }
      

      Install your project’s dependencies:

      Step 2 — Creating the Application Files

      We will create a website that offers users information about sharks.

      Open app.js in the main project directory to define the project’s routes:

      Add the following content to the file to create the Express application and Router objects, define the base directory, port, and host as variables, set the routes, and mount the router middleware along with the application’s static assets:

      ~/node_project/app.js

      var express = require("express");
      var app = express();
      var router = express.Router();
      
      var path = __dirname + '/views/';
      
      // Constants
      const PORT = 8080;
      const HOST = '0.0.0.0';
      
      router.use(function (req,res,next) {
        console.log("/" + req.method);
        next();
      });
      
      router.get("/",function(req,res){
        res.sendFile(path + "index.html");
      });
      
      router.get("/sharks",function(req,res){
        res.sendFile(path + "sharks.html");
      });
      
      app.use(express.static(path));
      app.use("/", router);
      
      app.listen(8080, function () {
        console.log('Example app listening on port 8080!')
      })
      

      Next, let’s add some static content to the application. Create the views directory:

      Open index.html:

      Add the following code to the file, which will import Boostrap and create a jumbotron component with a link to the more detailed sharks.html info page:

      ~/node_project/views/index.html

      <!DOCTYPE html>
      <html lang="en">
         <head>
            <title>About Sharks</title>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
            <link href="css/styles.css" rel="stylesheet">
            <link href='https://fonts.googleapis.com/css?family=Merriweather:400,700' rel='stylesheet' type='text/css'>
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
         </head>
         <body>
            <nav class="navbar navbar-inverse navbar-static-top">
               <div class="container">
                  <div class="navbar-header">
                     <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                     <span class="sr-only">Toggle navigation</span>
                     <span class="icon-bar"></span>
                     <span class="icon-bar"></span>
                     <span class="icon-bar"></span>
                     </button>
                     <a class="navbar-brand" href="#">Everything Sharks</a>
                  </div>
                  <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                     <ul class="nav navbar-nav mr-auto">
                        <li class="active"><a href="/">Home</a></li>
                        <li><a href="http://www.digitalocean.com/sharks">Sharks</a></li>
                     </ul>
                  </div>
               </div>
            </nav>
            <div class="jumbotron">
               <div class="container">
                  <h1>Want to Learn About Sharks?</h1>
                  <p>Are you ready to learn about sharks?</p>
                  <br>
                  <p><a class="btn btn-primary btn-lg" href="http://www.digitalocean.com/sharks" role="button">Get Shark Info</a></p>
               </div>
            </div>
            <div class="container">
               <div class="row">
                  <div class="col-md-6">
                     <h3>Not all sharks are alike</h3>
                     <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.</p>
                  </div>
                  <div class="col-md-6">
                     <h3>Sharks are ancient</h3>
                     <p>There is evidence to suggest that sharks lived up to 400 million years ago.</p>
                  </div>
               </div>
            </div>
         </body>
      </html>
      

      Next, open a file called sharks.html:

      Add the following code, which imports Bootstrap and the custom style sheet and offers users detailed information about certain sharks:

      ~/node_project/views/sharks.html

      <!DOCTYPE html>
      <html lang="en">
         <head>
            <title>About Sharks</title>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
            <link href="css/styles.css" rel="stylesheet">
            <link href='https://fonts.googleapis.com/css?family=Merriweather:400,700' rel='stylesheet' type='text/css'>
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
         </head>
         <nav class="navbar navbar-inverse navbar-static-top">
            <div class="container">
               <div class="navbar-header">
                  <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                  <span class="sr-only">Toggle navigation</span>
                  <span class="icon-bar"></span>
                  <span class="icon-bar"></span>
                  <span class="icon-bar"></span>
                  </button>
                  <a class="navbar-brand" href="#">Everything Sharks</a>
               </div>
               <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                  <ul class="nav navbar-nav mr-auto">
                     <li><a href="/">Home</a></li>
                     <li class="active"><a href="http://www.digitalocean.com/sharks">Sharks</a></li>
                  </ul>
               </div>
            </div>
         </nav>
         <div class="jumbotron text-center">
            <h1>Shark Info</h1>
         </div>
         <div class="container">
            <div class="row">
               <div class="col-md-6">
                  <p>
                  <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.</div>
                  <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
               </div>
               <div class="col-md-6">
                  <p>
                  <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                  <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
               </div>
            </div>
          </div>
         </body>
      </html>
      

      Finally, create the custom CSS style sheet that you’ve linked to in index.html and sharks.html by first creating a css folder in the views directory:

      Open the style sheet and add the following code, which will set the desired color and font for our pages:

      ~/node_project/views/css/styles.css

      .navbar {
              margin-bottom: 0;
      }
      
      body {
              background: #020A1B;
              color: #ffffff;
              font-family: 'Merriweather', sans-serif;
      }
      h1,
      h2 {
              font-weight: bold;
      }
      p {
              font-size: 16px;
              color: #ffffff;
      }
      
      
      .jumbotron {
              background: #0048CD;
              color: white;
              text-align: center;
      }
      .jumbotron p {
              color: white;
              font-size: 26px;
      }
      
      .btn-primary {
              color: #fff;
              text-color: #000000;
              border-color: white;
              margin-bottom: 5px;
      }
      
      img, video, audio {
              margin-top: 20px;
              max-width: 80%;
      }
      
      div.caption: {
              float: left;
              clear: both;
      }
      

      Start the application:

      Navigate your browser to http://your_server_ip:8080 or localhost:8080 if you are working locally. You will see the following landing page:

      Application Landing Page

      Click on the Get Shark Info button. You will see the following information page:

      Shark Info Page

      You now have an application up and running. When you are ready, quit the server by typing CTRL+C.

      Step 3 — Writing the Dockerfile

      In your project’s root directory, create the Dockerfile:

      Add the following code to the file:

      ~/node_project/Dockerfile

      
      FROM node:10
      
      RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
      
      WORKDIR /home/node/app
      
      COPY package*.json ./
      
      RUN npm install
      
      COPY . .
      
      COPY --chown=node:node . .
      
      USER node
      
      EXPOSE 8080
      
      CMD [ "npm", "start" ]
      

      This Dockerfile uses an alpine base image and ensures that application files are owned by the non-root node user that is provided by default by the Docker Node image.

      Next, add your local node modules, npm logs, Dockerfile, and .dockerignore to your .dockerignore file:

      ~/node_project/.dockerignore

      node_modules
      npm-debug.log
      Dockerfile
      .dockerignore
      

      Build the application image using the docker build command:

      • docker build -t your_dockerhub_username/nodejs-image-demo .

      The . specifies that the build context is the current directory.

      Check your images:

      You will see the following output:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 8 seconds ago 895MB node 10 f09e7c96b6de 17 hours ago 893MB

      Run the following command to build a container using this image:

      • docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo

      Inspect the list of your running containers with docker ps:

      You will see the following output:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 your_dockerhub_username/nodejs-image-demo "npm start" 8 seconds ago Up 7 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

      With your container running, you can now visit your application by navigating your browser to http://your_server_ip or localhost. You will see your application landing page once again:

      Application Landing Page

      Now that you have created an image for your application, you can push it to Docker Hub for future use.

      Step 4 — Using a Repository to Work with Images

      The first step to pushing the image is to log in to the your Docker Hub account:

      • docker login -u your_dockerhub_username -p your_dockerhub_password

      Logging in this way will create a ~/.docker/config.json file in your user’s home directory with your Docker Hub credentials.

      Push your image up using your own username in place of your_dockerhub_username:

      • docker push your_dockerhub_username/nodejs-image-demo

      If you would like, you can test the utility of the image registry by destroying your current application container and image and rebuilding them.

      First, list your running containers:

      You will see the following output:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 your_dockerhub_username/nodejs-image-demo "npm start" 3 minutes ago Up 3 minutes 0.0.0.0:80->8080/tcp nodejs-image-demo

      Using the CONTAINER ID listed in your output, stop the running application container. Be sure to replace the highlighted ID below with your own CONTAINER ID:

      List your all of your images with the -a flag:

      You will see the following output with the name of your image, your_dockerhub_username/nodejs-image-demo, along with the node image and the other images from your build:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 7 minutes ago 895MB <none> <none> e039d1b9a6a0 7 minutes ago 895MB <none> <none> dfa98908c5d1 7 minutes ago 895MB <none> <none> b9a714435a86 7 minutes ago 895MB <none> <none> 51de3ed7e944 7 minutes ago 895MB <none> <none> 5228d6c3b480 7 minutes ago 895MB <none> <none> 833b622e5492 8 minutes ago 893MB <none> <none> 5c47cc4725f1 8 minutes ago 893MB <none> <none> 5386324d89fb 8 minutes ago 893MB <none> <none> 631661025e2d 8 minutes ago 893MB node 10 f09e7c96b6de 17 hours ago 893MB

      Remove the stopped container and all of the images, including unused or dangling images, with the following command:

      With all of your images and containers deleted, you can now pull the application image from Docker Hub:

      • docker pull your_dockerhub_username/nodejs-image-demo

      List your images once again:

      You will see your application image:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 11 minutes ago 895MB

      You can now rebuild your container using the command from Step 3:

      • docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo

      List your running containers:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f6bc2f50dff6 your_dockerhub_username/nodejs-image-demo "npm start" 4 seconds ago Up 3 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

      Visit http://your_server_ip or localhost once again to view your running application.

      Here are links to more detailed guides related to this tutorial:

      You can also look at the longer series on From Containers to Kubernetes with Node.js, from which this tutorial is adapated.

      Additionally, see our full library of Docker resources for more on Docker.



      Source link

      How to Containerize a Laravel 6 Application for Development with Docker Compose on Ubuntu 18.04


      Introduction

      To containerize an application refers to the process of adapting an application and its components in order to be able to run it in lightweight environments known as containers. Such environments are isolated and disposable, and can be leveraged for developing, testing, and deploying applications to production.

      In this guide, we’ll use Docker Compose to containerize a Laravel 6 application for development. When you’re finished, you’ll have a demo Laravel application running on three separate service containers:

      • An app service running PHP7.4-FPM;
      • A db service running MySQL 5.7;
      • An nginx service that uses the app service to parse PHP code before serving the Laravel application to the final user.

      To allow for a streamlined development process and facilitate application debugging, we’ll keep application files in sync by using shared volumes. We’ll also see how to use docker-compose exec commands to run Composer and Artisan on the app container.

      Prerequisites

      Step 1 — Obtaining the Demo Application

      To get started, we’ll fetch the demo Laravel application from its Github repository. We’re interested in the tutorial-01 branch, which contains the basic Laravel application we’ve created in the first guide of this series.

      To obtain the application code that is compatible with this tutorial, download release tutorial-1.0.1 to your home directory with:

      • cd ~
      • curl -L https://github.com/do-community/travellist-laravel-demo/archive/tutorial-1.0.1.zip -o travellist.zip

      We’ll need the unzip command to unpack the application code. In case you haven’t installed this package before, do so now with:

      • sudo apt update
      • sudo apt install unzip

      Now, unzip the contents of the application and rename the unpacked directory for easier access:

      • unzip travellist.zip
      • mv travellist-laravel-demo-tutorial-1.0.1 travellist-demo

      Navigate to the travellist-demo directory:

      In the next step, we’ll create a .env configuration file to set up the application.

      Step 2 — Setting Up the Application’s .env File

      The Laravel configuration files are located in a directory called config, inside the application’s root directory. Additionally, a .env file is used to set up environment-dependent configuration, such as credentials and any information that might vary between deploys. This file is not included in revision control.

      Warning: The environment configuration file contains sensitive information about your server, including database credentials and security keys. For that reason, you should never share this file publicly.

      The values contained in the .env file will take precedence over the values set in regular configuration files located at the config directory. Each installation on a new environment requires a tailored environment file to define things such as database connection settings, debug options, application URL, among other items that may vary depending on which environment the application is running.

      We’ll now create a new .env file to customize the configuration options for the development environment we’re setting up. Laravel comes with an example.env file that we can copy to create our own:

      Open this file using nano or your text editor of choice:

      The current .env file from the travellist demo application contains settings to use a local MySQL database, with 127.0.0.1 as database host. We need to update the DB_HOST variable so that it points to the database service we will create in our Docker environment. In this guide, we’ll call our database service db. Go ahead and replace the listed value of DB_HOST with the database service name:

      .env

      APP_NAME=Travellist
      APP_ENV=dev
      APP_KEY=
      APP_DEBUG=true
      APP_URL=http://localhost:8000
      
      LOG_CHANNEL=stack
      
      DB_CONNECTION=mysql
      DB_HOST=db
      DB_PORT=3306
      DB_DATABASE=travellist
      DB_USERNAME=travellist_user
      DB_PASSWORD=password
      ...
      

      Feel free to also change the database name, username, and password, if you wish. These variables will be leveraged in a later step where we’ll set up the docker-compose.yml file to configure our services.

      Save the file when you’re done editing. If you used nano, you can do that by pressing Ctrl+x, then Y and Enter to confirm.

      Step 3 — Setting Up the Application’s Dockerfile

      Although both our MySQL and Nginx services will be based on default images obtained from the Docker Hub, we still need to build a custom image for the application container. We’ll create a new Dockerfile for that.

      Our travellist image will be based on the php:7.4-fpm official PHP image from Docker Hub. On top of that basic PHP-FPM environment, we’ll install a few extra PHP modules and the Composer dependency management tool.

      We’ll also create a new system user; this is necessary to execute artisan and composer commands while developing the application. The uid setting ensures that the user inside the container has the same uid as your system user on your host machine, where you’re running Docker. This way, any files created by these commands are replicated in the host with the correct permissions. This also means that you’ll be able to use your code editor of choice in the host machine to develop the application that is running inside containers.

      Create a new Dockerfile with:

      Copy the following contents to your Dockerfile:

      Dockerfile

      FROM php:7.4-fpm
      
      # Arguments defined in docker-compose.yml
      ARG user
      ARG uid
      
      # Install system dependencies
      RUN apt-get update && apt-get install -y 
          git 
          curl 
          libpng-dev 
          libonig-dev 
          libxml2-dev 
          zip 
          unzip
      
      # Clear cache
      RUN apt-get clean && rm -rf /var/lib/apt/lists/*
      
      # Install PHP extensions
      RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
      
      # Get latest Composer
      COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
      
      # Create system user to run Composer and Artisan Commands
      RUN useradd -G www-data,root -u $uid -d /home/$user $user
      RUN mkdir -p /home/$user/.composer && 
          chown -R $user:$user /home/$user
      
      # Set working directory
      WORKDIR /var/www
      
      USER $user
      
      

      Don’t forget to save the file when you’re done.

      Our Dockerfile starts by defining the base image we’re using: php:7.4-fpm.

      After installing system packages and PHP extensions, we install Composer by copying the composer executable from its latest official image to our own application image.

      A new system user is then created and set up using the user and uid arguments that were declared at the beginning of the Dockerfile. These values will be injected by Docker Compose at build time.

      Finally, we set the default working dir as /var/www and change to the newly created user. This will make sure you’re connecting as a regular user, and that you’re on the right directory, when running composer and artisan commands on the application container.

      Step 4 — Setting Up Nginx Configuration and Database Dump Files

      When creating development environments with Docker Compose, it is often necessary to share configuration or initialization files with service containers, in order to set up or bootstrap those services. This practice facilitates making changes to configuration files to fine-tune your environment while you’re developing the application.

      We’ll now set up a folder with files that will be used to configure and initialize our service containers.

      To set up Nginx, we’ll share a travellist.conf file that will configure how the application is served. Create the docker-compose/nginx folder with:

      • mkdir -p docker-compose/nginx

      Open a new file named travellist.conf within that directory:

      • nano docker-compose/nginx/travellist.conf

      Copy the following Nginx configuration to that file:

      docker-compose/nginx/travellist.conf

      
      server {
          listen 80;
          index index.php index.html;
          error_log  /var/log/nginx/error.log;
          access_log /var/log/nginx/access.log;
          root /var/www/public;
          location ~ .php$ {
              try_files $uri =404;
              fastcgi_split_path_info ^(.+.php)(/.+)$;
              fastcgi_pass app:9000;
              fastcgi_index index.php;
              include fastcgi_params;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
              fastcgi_param PATH_INFO $fastcgi_path_info;
          }
          location / {
              try_files $uri $uri/ /index.php?$query_string;
              gzip_static on;
          }
      }
      

      This file will configure Nginx to listen on port 80 and use index.php as default index page. It will set the document root to /var/www/public, and then configure Nginx to use the app service on port 9000 to process *.php files.

      Save and close the file when you’re done editing.

      To set up the MySQL database, we’ll share a database dump that will be imported when the container is initialized. This is a feature provided by the MySQL 5.7 image we’ll be using on that container.

      Create a new folder for your MySQL initialization files inside the docker-compose folder:

      • mkdir docker-compose/mysql

      Open a new .sql file:

      • nano docker-compose/mysql/init_db.sql

      The following MySQL dump is based on the database we’ve set up in our Laravel on LEMP guide. It will create a new table named places. Then, it will populate the table with a set of sample places.

      Add the following code to the file:

      docker-compose/mysql/db_init.sql

      DROP TABLE IF EXISTS `places`;
      
      CREATE TABLE `places` (
        `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
        `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
        `visited` tinyint(1) NOT NULL DEFAULT '0',
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
      
      INSERT INTO `places` (name, visited) VALUES ('Berlin',0),('Budapest',0),('Cincinnati',1),('Denver',0),('Helsinki',0),('Lisbon',0),('Moscow',1),('Nairobi',0),('Oslo',1),('Rio',0),('Tokyo',0);
      

      The places table contains three fields: id, name, and visited. The visited field is a flag used to identify the places that are still to go. Feel free to change the sample places or include new ones. Save and close the file when you’re done.

      We’ve finished setting up the application’s Dockerfile and the service configuration files. Next, we’ll set up Docker Compose to use these files when creating our services.

      Step 5 — Creating a Multi-Container Environment with Docker Compose

      Docker Compose enables you to create multi-container environments for applications running on Docker. It uses service definitions to build fully customizable environments with multiple containers that can share networks and data volumes. This allows for a seamless integration between application components.

      To set up our service definitions, we’ll create a new file called docker-compose.yml. Typically, this file is located at the root of the application folder, and it defines your containerized environment, including the base images you will use to build your containers, and how your services will interact.

      We’ll define three different services in our docker-compose.yml file: app, db, and nginx.

      The app service will build an image called travellist, based on the Dockerfile we’ve previously created. The container defined by this service will run a php-fpm server to parse PHP code and send the results back to the nginx service, which will be running on a separate container. The mysql service defines a container running a MySQL 5.7 server. Our services will share a bridge network named travellist.

      The application files will be synchronized on both the app and the nginx services via bind mounts. Bind mounts are useful in development environments because they allow for a performant two-way sync between host machine and containers.

      Create a new docker-compose.yml file at the root of the application folder:

      A typical docker-compose.yml file starts with a version definition, followed by a services node, under which all services are defined. Shared networks are usually defined at the bottom of that file.

      To get started, copy this boilerplate code into your docker-compose.yml file:

      docker-compose.yml

      version: "3.7"
      services:
      
      
      networks:
        travellist:
          driver: bridge
      

      We’ll now edit the services node to include the app, db and nginx services.

      The app Service

      The app service will set up a container named travellist-app. It builds a new Docker image based on a Dockerfile located in the same path as the docker-compose.yml file. The new image will be saved locally under the name travellist.

      Even though the document root being served as the application is located in the nginx container, we need the application files somewhere inside the app container as well, so we’re able to execute command line tasks with the Laravel Artisan tool.

      Copy the following service definition under your services node, inside the docker-compose.yml file:

      docker-compose.yml

        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: travellist
          container_name: travellist-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - travellist
      

      These settings do the following:

      • build: This configuration tells Docker Compose to build a local image for the app service, using the specified path (context) and Dockerfile for instructions. The arguments user and uid are injected into the Dockerfile to customize user creation commands at build time.
      • image: The name that will be used for the image being built.
      • container_name: Sets up the container name for this service.
      • restart: Always restart, unless the service is stopped.
      • working_dir: Sets the default directory for this service as /var/www.
      • volumes: Creates a shared volume that will synchronize contents from the current directory to /var/www inside the container. Notice that this is not your document root, since that will live in the nginx container.
      • networks: Sets up this service to use a network named travellist.

      The db Service

      The db service uses a pre-built MySQL 5.7 image from Docker Hub. Because Docker Compose automatically loads .env variable files located in the same directory as the docker-compose.yml file, we can obtain our database settings from the Laravel .env file we created in a previous step.

      Include the following service definition in your services node, right after the app service:

      docker-compose.yml

        db:
          image: mysql:5.7
          container_name: travellist-db
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          volumes:
            - ./docker-compose/mysql:/docker-entrypoint-initdb.d
          networks:
            - travellist
      

      These settings do the following:

      • image: Defines the Docker image that should be used for this container. In this case, we’re using a MySQL 5.7 image from Docker Hub.
      • container_name: Sets up the container name for this service: travellist-db.
      • restart: Always restart this service, unless it is explicitly stopped.
      • environment: Defines environment variables in the new container. We’re using values obtained from the Laravel .env file to set up our MySQL service, which will automatically create a new database and user based on the provided environment variables.
      • volumes: Creates a volume to share a .sql database dump that will be used to initialize the application database. The MySQL image will automatically import .sql files placed in the /docker-entrypoint-initdb.d directory inside the container.
      • networks: Sets up this service to use a network named travellist.

      The nginx Service

      The nginx service uses a pre-built Nginx image on top of Alpine, a lightweight Linux distribution. It creates a container named travellist-nginx, and it uses the ports definition to create a redirection from port 8000 on the host system to port 80 inside the container.

      Include the following service definition in your services node, right after the db service:

      docker-compose.yml

        nginx:
          image: nginx:1.17-alpine
          container_name: travellist-nginx
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d
          networks:
            - travellist
      

      These settings do the following:

      • image: Defines the Docker image that should be used for this container. In this case, we’re using the Alpine Nginx 1.17 image.
      • container_name: Sets up the container name for this service: travellist-nginx.
      • restart: Always restart this service, unless it is explicitly stopped.
      • ports: Sets up a port redirection that will allow external access via port 8000 to the web server running on port 80 inside the container.
      • volumes: Creates two shared volumes. The first one will synchronize contents from the current directory to /var/www inside the container. This way, when you make local changes to the application files, they will be quickly reflected in the application being served by Nginx inside the container. The second volume will make sure our Nginx configuration file, located at docker-compose/nginx/travellist.conf, is copied to the container’s Nginx configuration folder.
      • networks: Sets up this service to use a network named travellist.

      Finished docker-compose.yml File

      This is how our finished docker-compose.yml file looks like:

      docker-compose.yml

      version: "3.7"
      services:
        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: travellist
          container_name: travellist-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - travellist
      
        db:
          image: mysql:5.7
          container_name: travellist-db
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
            SERVICE_TAGS: dev
            SERVICE_NAME: mysql
          volumes:
            - ./docker-compose/mysql:/docker-entrypoint-initdb.d
          networks:
            - travellist
      
        nginx:
          image: nginx:alpine
          container_name: travellist-nginx
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d/
          networks:
            - travellist
      
      networks:
        travellist:
          driver: bridge
      

      Make sure you save the file when you’re done.

      Step 6 — Running the Application with Docker Compose

      We’ll now use docker-compose commands to build the application image and run the services we specified in our setup.

      Build the app image with the following command:

      This command might take a few minutes to complete. You’ll see output similar to this:

      Output

      Building app Step 1/11 : FROM php:7.4-fpm ---> fa37bd6db22a Step 2/11 : ARG user ---> Running in f71eb33b7459 Removing intermediate container f71eb33b7459 ---> 533c30216f34 Step 3/11 : ARG uid ---> Running in 60d2d2a84cda Removing intermediate container 60d2d2a84cda ---> 497fbf904605 Step 4/11 : RUN apt-get update && apt-get install -y git curl libpng-dev libonig-dev ... Step 7/11 : COPY --from=composer:latest /usr/bin/composer /usr/bin/composer ---> e499f74896e3 Step 8/11 : RUN useradd -G www-data,root -u $uid -d /home/$user $user ---> Running in 232ef9c7dbd1 Removing intermediate container 232ef9c7dbd1 ---> 870fa3220ffa Step 9/11 : RUN mkdir -p /home/$user/.composer && chown -R $user:$user /home/$user ---> Running in 7ca8c0cb7f09 Removing intermediate container 7ca8c0cb7f09 ---> 3d2ef9519a8e Step 10/11 : WORKDIR /var/www ---> Running in 4a964f91edfa Removing intermediate container 4a964f91edfa ---> 00ada639da21 Step 11/11 : USER $user ---> Running in 9f8e874fede9 Removing intermediate container 9f8e874fede9 ---> fe176ff4702b Successfully built fe176ff4702b Successfully tagged travellist:latest

      When the build is finished, you can run the environment in background mode with:

      Output

      Creating travellist-db ... done Creating travellist-app ... done Creating travellist-nginx ... done

      This will run your containers in the background. To show information about the state of your active services, run:

      You’ll see output like this:

      Output

      Name Command State Ports ------------------------------------------------------------------------------- travellist-app docker-php-entrypoint php-fpm Up 9000/tcp travellist-db docker-entrypoint.sh mysqld Up 3306/tcp, 33060/tcp travellist-nginx nginx -g daemon off; Up 0.0.0.0:8000->80/tcp

      Your environment is now up and running, but we still need to execute a couple commands to finish setting up the application. You can use the docker-compose exec command to execute commands in the service containers, such as an ls -l to show detailed information about files in the application directory:

      • docker-compose exec app ls -l

      Output

      total 256 -rw-rw-r-- 1 sammy 1001 738 Jan 15 16:46 Dockerfile -rw-rw-r-- 1 sammy 1001 101 Jan 7 08:05 README.md drwxrwxr-x 6 sammy 1001 4096 Jan 7 08:05 app -rwxr-xr-x 1 sammy 1001 1686 Jan 7 08:05 artisan drwxrwxr-x 3 sammy 1001 4096 Jan 7 08:05 bootstrap -rw-rw-r-- 1 sammy 1001 1501 Jan 7 08:05 composer.json -rw-rw-r-- 1 sammy 1001 179071 Jan 7 08:05 composer.lock drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 config drwxrwxr-x 5 sammy 1001 4096 Jan 7 08:05 database drwxrwxr-x 4 sammy 1001 4096 Jan 15 16:46 docker-compose -rw-rw-r-- 1 sammy 1001 1015 Jan 15 16:45 docker-compose.yml -rw-rw-r-- 1 sammy 1001 1013 Jan 7 08:05 package.json -rw-rw-r-- 1 sammy 1001 1405 Jan 7 08:05 phpunit.xml drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 public -rw-rw-r-- 1 sammy 1001 273 Jan 7 08:05 readme.md drwxrwxr-x 6 sammy 1001 4096 Jan 7 08:05 resources drwxrwxr-x 2 sammy 1001 4096 Jan 7 08:05 routes -rw-rw-r-- 1 sammy 1001 563 Jan 7 08:05 server.php drwxrwxr-x 5 sammy 1001 4096 Jan 7 08:05 storage drwxrwxr-x 4 sammy 1001 4096 Jan 7 08:05 tests -rw-rw-r-- 1 sammy 1001 538 Jan 7 08:05 webpack.mix.js

      We’ll now run composer install to install the application dependencies:

      • docker-compose exec app composer install

      You’ll see output like this:

      Output

      Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 85 installs, 0 updates, 0 removals - Installing doctrine/inflector (1.3.1): Downloading (100%) - Installing doctrine/lexer (1.2.0): Downloading (100%) - Installing dragonmantank/cron-expression (v2.3.0): Downloading (100%) - Installing erusev/parsedown (1.7.4): Downloading (100%) - Installing symfony/polyfill-ctype (v1.13.1): Downloading (100%) - Installing phpoption/phpoption (1.7.2): Downloading (100%) - Installing vlucas/phpdotenv (v3.6.0): Downloading (100%) - Installing symfony/css-selector (v5.0.2): Downloading (100%) … Generating optimized autoload files > IlluminateFoundationComposerScripts::postAutoloadDump > @php artisan package:discover --ansi Discovered Package: facade/ignition Discovered Package: fideloper/proxy Discovered Package: laravel/tinker Discovered Package: nesbot/carbon Discovered Package: nunomaduro/collision Package manifest generated successfully.

      The last thing we need to do before testing the application is to generate a unique application key with the artisan Laravel command-line tool. This key is used to encrypt user sessions and other sensitive data:

      • docker-compose exec app php artisan key:generate

      Output

      Application key set successfully.

      Now go to your browser and access your server’s domain name or IP address on port 8000:

      http://server_domain_or_IP:8000
      

      You’ll see a page like this:

      Demo Laravel Application

      You can use the logs command to check the logs generated by your services:

      • docker-compose logs nginx
      Attaching to travellist-nginx
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:25 +0000] "GET / HTTP/1.1" 200 626 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:26 +0000] "GET /favicon.ico HTTP/1.1" 200 0 "http://localhost:8000/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      travellist-nginx | 192.168.160.1 - - [23/Jan/2020:13:57:42 +0000] "GET / HTTP/1.1" 200 626 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
      …
      

      If you want to pause your Docker Compose environment while keeping the state of all its services, run:

      Output

      Pausing travellist-db ... done Pausing travellist-nginx ... done Pausing travellist-app ... done

      You can then resume your services with:

      Output

      Unpausing travellist-app ... done Unpausing travellist-nginx ... done Unpausing travellist-db ... done

      To shut down your Docker Compose environment and remove all of its containers, networks, and volumes, run:

      Output

      Stopping travellist-nginx ... done Stopping travellist-db ... done Stopping travellist-app ... done Removing travellist-nginx ... done Removing travellist-db ... done Removing travellist-app ... done Removing network travellist-laravel-demo_travellist

      For an overview of all Docker Compose commands, please check the Docker Compose command-line reference.

      Conclusion

      In this guide, we’ve set up a Docker environment with three containers using Docker Compose to define our infrastructure in a YAML file.

      From this point on, you can work on your Laravel application without needing to install and set up a local web server for development and testing. Moreover, you’ll be working with a disposable environment that can be easily replicated and distributed, which can be helpful while developing your application and also when moving towards a production environment.



      Source link