Docker Swarm Docker Swarm vs Kubernetes: A Practical Comparison Link: https://betterstack.com/community/guides/scaling-docker/docker-swarm-kubernetes/ Docker Docker Swarm Kubernetes Donald Le Updated on December 22, 2023 Contents What is Docker Swarm? What is Kubernetes? Comparing Docker Swarm and Kubernetes Prerequisites Setting up the demo application Deploying the application with Docker Swarm Deploying the application with Kubernetes Use cases of Kubernetes vs. Docker Swarm Final thoughts In the world of container orchestration, two prominent platforms have emerged as leaders:  Docker Swarm  and  Kubernete . As organizations increasingly adopt containerization to achieve scalability, resilience, and efficient resource utilization, choosing between these two solutions becomes crucial. This article will compare both platforms, exploring their features, strengths, and trade-offs. By understanding their differences and use cases, you will gain valuable insights to select the ideal container orchestration tool for your specific needs. We will examine various aspects, including installation and setup, container compatibility, scalability, high availability, networking, ease of use, ecosystem, and security. Through practical hands-on example, you will get a taste of how each platform tackles common challenges in deploying and managing containerized applications. Whether you are new to container orchestration or seeking to transition from one platform to another, this article aims to provide comprehensive knowledge to make an educated choice. By the end, you be well-equipped to decide which option aligns best with your requirements and goals. Without any further ado, let's learn what Docker Swarm is first.   Centralize & visualize your logs. Query everything with SQL. Explore more   What is Docker Swarm? Docker Swarm is a container orchestration tool for managing and deploying applications using a containerization approach. It allows you to create a cluster of Docker nodes (known as a Swarm) and deploy services to the cluster. Docker Swarm is lightweight and straightforward, allowing you to  containerize and deploy your software project in minutes . To manage and deploy applications efficiently, Docker Swarm offers the following capabilities: Cluster Management : You can create a cluster of nodes to manage the application containers. A node can be a manager or a worker (or both). Service Abstraction : Docker Swarm introduces the "Service" component, which lets you set the network configurations, resource limits, and number of container replicas. By managing the services, Docker Swarm ensures the application's desired state is maintained, Load Balancing : Swarm offers built-in load balancing, which allows services inside the cluster to interact without the need for you to define the load balancer file manually. Rolling Back : Rolling back to a specific service to its previous version is fully supported in case of a failed deployment. Fault Tolerance : Docker Swarm automatically checks for failures in nodes and containers to generate new ones to allow users to keep using the application without noticing any problems. Scalability : With Docker Swarm, you have the flexibility to adjust the number of replicas for your containers easily. This allows you to scale your application up or down based on the changing demands of your workload. Docker Swarm architecture Below is the diagram representing Docker Swarm Architecture: In summary, the Swarm cluster consists of several vital components that work together to manage and deploy containers efficiently: Manager nodes are responsible for receiving client commands and assigning tasks to worker nodes. Inside each manager node, we have the Service Discovery and Scheduler component. The former is responsible for collecting information on the worker nodes, while the latter finds suitable worker nodes to assign tasks to. Worker nodes are solely responsible for executing the tasks assigned by a manager node. In the above diagram, the deployment steps typically work like this: The client machines send commands to a manager node, let's say to deploy a new PostgreSQL database to the Swarm cluster. The Swarm manager finds the appropriate worker node to assign tasks for creating the new deployment. Finally, the worker node will create new containers for the PostgreSQL database and manage the containers' states. Now that you've understood what Docker Swarm is and how it works let's learn about Kubernetes. What is Kubernetes? Kubernetes is a container orchestration tool for managing and deploying containerized applications. It supports a wide variety of container runtimes, including Docker, Containerd, Mirantis, and others, but the most commonly used runtime is Docker. Kubernetes is feature-rich and highly configurable, which makes it ideal for deploying complex applications, but it requires a steep learning curve. Some of the key features of Kubernetes are listed below: Container Orchestration with Pods : A pod is the smallest and simplest deployment unit. Kubernetes allows you to deploy applications by creating and managing one or more containers in pods. Service Discovery : Kubernetes allows containers to interact with each other easily within the same cluster. Load Balancing : Kubernetes' load balancer allows access to a group of pods via an external network. Clients from outside the Kubernetes cluster can access the pods running inside the Kubernetes cluster via the balancer's external IP. If any of the pods in the group goes down, client requests will automatically be forwarded to other pods, allowing the deployed applications to be highly available. Automatic Scaling :  You can scale-in or scale-out your application  by adjusting number of deployed containers based on resource utilization or custom-defined metrics. Self-Healing : If one of your pods goes down, Kubernetes will try to recreate that pod automatically so that normal operation is restored. Persistent Storage : It provides support for various storage options, allowing you to persist data beyond the lifecycle of individual pods. Configuration and Secrets Management : Kubernetes allows you to store and manage configuration data and secrets securely, decoupling sensitive information from the application code. Rolling back : If there's a problem with your deployment, you can easily transition to a previous version of the application. Resource Management : With Kubernetes, you can flexibly set the resource constraints when deploying your application. Extensibility : Kubernetes has a vast ecosystem and can be extended with numerous plugins and tools for added functionality. You can also interact with Kubernetes components using Kubernetes APIs to deploy your application programmatically. Kubernetes architecture The above diagram illustrates the Kubernetes architecture. It contains the following components: Control Plane : This is a set of components responsible for managing the operation of a Kubernetes cluster. The main components of the Kubernetes control plane are: Kube API Server: Provides an interface to interact with the Kubernetes cluster. It exposes the Kubernetes API so that clients from outside the cluster can communicate and control it. Etcd: It is a distributed key-value store for storing configuration data and the desired state of the cluster. Scheduler: It decides where to place newly created pods within the cluster based on resource requirements, node availability, and other defined rules. It also continuously monitors the cluster's resources to balance the workload across nodes. Kube Controller Manager: It is responsible for managing and maintaining the desired state of various Kubernetes components such as pods, services, or volumes. It continuously checks the state of Kubernetes components, whether they match expectations or not. If they don't, the controller-manager will take action to restore order to the problematic components. Kubernetes node : The Kubernetes node (which could be a physical or virtual machine) is responsible for executing the tasks assigned by the control plane. Here are the main components and concepts related to Kubernetes' nodes: Kubelet: The Kubelet is an agent that runs on each node and manages the containers and their lifecycle. It communicates with the control plane, receives instructions about which containers to run, and ensures that the containers are in the desired state. The Kubelet also monitors the health of the containers and reports their status back to the control plane. Pod: Kubernetes pod is the smallest component of Kubernetes cluster. It represents a single instance of a running process in the cluster. Inside a pod you have the container runtime, which is responsible for pulling container images from a registry, creating container instances, and managing their execution. In the Kubernetes architecture diagram above, the execution steps work like this: The client from outside the Kubernetes cluster will interact with the cluster by executing  kubectl  command to the Kubernetes controller plane. The controller plane will assign deployment tasks to the Kubernetes Node. The Kubernetes node will create new pods with container runtime for the container execution. To allow users outside the cluster to interact with the deployed app, the client sends requests to the Kubernetes control plane to create a load balancer. Finally, users can interact with the deployed application via the load balancer IP Address. Now that you understand what Kubernetes is, let's continue to the next section to learn the differences between Docker Swarm and Kubernetes. Comparing Docker Swarm and Kubernetes Although Docker Swarm and Kubernetes offer the capabilities to orchestrate containers, they are fundamentally different and serve different use cases. Criteria Docker Swarm Kubernetes Installation and setup Easy to set up using the  docker  command Complicated to manually set up the Kubernetes cluster Types of containers they support Only works with Docker containers Supports Containerd, Docker, CRI-O, and others High Availability Provides basic configuration to support high availability Offers feature-rich support for high availability Popularity Popular Popular Networking Support basic networking features Advanced features support for networking GUI support Yes Yes Learning curve Easy to get started Requires a steeper learning curve Complexity Simple and lightweight Complicated and offer a lot of features Load balancing Automatic load-balancing Manual load-balancing CLIs Do not need to install other CLI Need to install other CLIs such as  kubectl Scalability Does not support automatic scaling Supports automatic scaling Security Only supports TLS Supports RBAC, SSL/TLS, and secret management Setup and installation Regarding installation and setup, Docker Swarm is easier to set up than Kubernetes. Assuming Docker is already installed, you only need to run  docker swarm init  to create the cluster, then attach a node to the cluster using  docker swarm join . The steps for setting up a Kubernetes cluster are not as straightforward without using cloud platforms. However, there are some tools ease the process such as Kubeadm, or Kubespray. For example, to create a new cluster using Kubeadm you need to: Set up the Docker runtime on all the nodes. Install Kubeadm on all the nodes. Initiate Kubernetes Control Plane on the master node. Install the network plugin. Join the worker node to the master node. Using  kubectl get node  to check whether all the nodes are added to the Kubernetes cluster. Types of containers they support Docker Swarm only supports Docker containers, but Kubernetes supports any container runtime that implements its  Container Runtime Interface , including Docker, Containerd, CRI-O, Mirantis, and others, giving you many options to choose the one that best fits your use cases. High availability Docker Swarm provides built-in high availability for services. It automatically replicates services across multiple nodes in the Swarm cluster, ensuring that containers are distributed and balanced for fault tolerance. On the other hand, Kubernetes provides a comprehensive set of features to achieve high availability, such as advanced scheduling to define pod placement based on node availability or resource utilization. Factors such as node health, resource constraints, and workload demands are also considered when distributing pods across the cluster. Popularity Both Docker Swarm and Kubernetes are popular in the industry and have been battle-tested for a wide variety of use cases. Docker Swarm is often used where simplicity and fast deployment are the primary considerations, while Kubernetes is well-suited for complex applications that demand advanced features and capabilities. It excels in scenarios where high availability, scalability, and flexibility are crucial requirements. Networking Both Docker Swarm and Kubernetes offer networking support. Docker Swarm provides simpler built-in overlay networking capabilities, while Kubernetes supports a more extensive and flexible networking model through its wide range of plugins and advanced networking features. GUI support Both Docker Swarm and Kubernetes have GUI tools to help you easily interact with them. With Docker Swarm, you have  Swarmpit  or  Shipyard . For Kubernetes, you have  Kubernetes Lens ,  Kube-dashboard , or  Octant . Learning curve Docker Swarm is easy to learn and start with since it is lightweight and provides a simple approach to managing Docker containers. Kubernetes, however, has a much steeper learning curve since it offers more features and flexibility for deploying and managing containers. Complexity Docker Swarm is designed to be lightweight and easy to use while Kubernetes is designed to have a lot of features and support to deploy complex applications. Thus, Kubernetes is much more complicated to operate compared to Docker Swarm. Load Balancing Docker Swarm offers an automatic load balancing mechanism, ensuring seamless communication and interaction between containers within the Docker Swarm cluster. The load balancing functionality is built-in, requiring minimal configuration. In contrast, Kubernetes provides a more customizable approach to load balancing. It allows you to define and configure load balancers based on your specific requirements. While this requires some manual setup, it grants you greater control over the load balancing configuration within the Kubernetes cluster. Command-line tools When working with Docker Swarm, there is no need to install an additional command line tool specifically for cluster management. The standard  docker  command is sufficient to create and interact with the Swarm cluster. However, Kubernetes requires the installation of the  kubectl  command line tool to work with the cluster.  kubectl  is the primary way to interact with a Kubernetes cluster and it provides extensive functionality for managing deployments, services, pods, and other resources within the Kubernetes environment. Scalability Kubernetes allows you to automatically scale in or scale out the containers based on resource utilization or other factors. Docker Swarm, on the other hand,  does not support auto-scaling ability . Security Docker Swarm and Kubernetes were built with security in mind, and they both provide several features and mechanisms to deploy applications safely. However, Kubernetes supports more authentication and authorization mechanisms such as Role-Based Access Control (RBAC), secure access layers (SSL), TLS protocol, and secret management. Now that you have a clearer understanding of the distinctions between Docker Swarm and Kubernetes, we will now focus on deploying a sample Go application to both platforms. This hands-on experience will provide you with practical insights into the deployment process, showcasing the unique features and considerations of each platform. Prerequisites Before following through with the rest of this tutorial, you need to meet the following requirements: A ready-to-use Linux machine, such as an  Ubuntu version 22.04  server. A recent version of Go  installed on your machine. A ready-to-use  Git  command line for cloning the sample application from its GitHub repo. A recent version of Docker  installed and accessible without using  sudo . A free  DockerHub  account for storing and sharing Docker images. An already installed  PostgreSQL  instance. Setting up the demo application To fully understand how Docker Swarm and Kubernetes work and how they differ, you will deploy a demo application using both tools. This application is a blog service that allows users to create, update, retrieve, and delete blog posts. It is built using the Go programming language, and its data is stored in PostgreSQL. However, you don't need to be familiar with Go or PostgreSQL to follow through. If you prefer, you can use an application stack that you're more familiar with and just follow the deployment steps to practice using Docker Swarm and Kubernetes. To test out the application's functionality, let's try to run it directly on the local machine before deploying it through Docker Swarm and Kubernetes. 1. Cloning the GitHub repository In this step, you will clone the  this GitHub repository  to your machine using the commands below:   git clone https://github.com/betterstack-community/docker-swarm-kubernetes   cd betterstack-swarm-kubernetes The structure of the directory should look like this:   ├── config ├── Dockerfile ├── go.mod ├── go.sum ├── handler ├── kubernetes ├── main.go └── swarm The  swarm  and  kubernetes  directories, along with the  Dockerfile  file are for deploying the app to Docker Swarm and Kubernetes platform. You will temporarily skip them for now. The rest of the directories and files constitute the Go application. 2. Creating a PostgreSQL database in the local environment Assuming PostgreSQL is installed on your machine, you can go ahead and access the  psql  command-line using the default  postgres  user by running the following command:   sudo -u postgres psql In the  psql  interface, create a new user named "blog_user":   CREATE USER blog_user; Run the following command to list all existing users in the PostgreSQL database. You should see the  blog_user  user there.   \du  Output List of roles Role name | Attributes | Member of -----------+------------------------------------------------------------+----------- blog_user | | {} postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} Next, change the password of the  blog_user  user to "blog_password":   ALTER ROLE blog_user WITH PASSWORD 'blog_password'; Afterward, create a new  blog_db  database by running the following command:   CREATE DATABASE blog_db; Finally, quit the current session using  \q , and then log in again to  blog_db  database using  blog_user  user. Enter the password for the blog user if prompted:   psql -U blog_user -d blog_db -h localhost At this stage, you can create a new  posts  table within the  blog_db  database by running the following command:   CREATE TABLE IF NOT EXISTS posts ( ID SERIAL NOT NULL PRIMARY KEY, BODY TEXT NOT NULL, CREATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UPDATED_AT TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); Now that you've successfully set up the database let's go ahead and run the application in the local environment. 3. Starting the application To bring up the app in the local environment, you need to install the required dependencies first:   go mod tidy Then run the following commands to set the necessary environment variables for the Go application to access the PostgreSQL database:   export POSTGRES_PASSWORD=blog_password   export POSTGRES_DB=blog_db   export POSTGRES_USER=blog_user   export POSTGRES_HOST=localhost Afterward, run the following command to start the Go application:   go run main.go  Output You are connected to the database Once the application is up and running open a separate terminal and run the following command to create a new blog entry:   curl --location 'http://localhost:8081/blog' \ --header 'Content-Type: application/json' \ --data '{ "body":"this is a great blog" }' Then run the following command to get the entry with an id of  1 :   curl --location 'http://localhost:8081/blog/1' You should see the result below:  Output {"id":1,"body":"this is a great blog","created_at":"2023-05-13T15:03:40.461732Z","updated_at":"2023-05-13T15:03:40.461732Z"} At this point, you've successfully set up the app in the local environment and interacted with its APIs to confirm that it's in working order. Let's now move on to the next section where you will deploy the application using Docker Swarm. Deploying the application with Docker Swarm Since you no longer need the PostgreSQL database service to run, you can stop it by running the following command:   sudo service postgresql stop You can also stop the current application by pressing  CTRL+C  in the terminal. 1. Create a Docker Swarm cluster In a Docker Swarm cluster, you will have one or more manager nodes to distribute tasks to one or more worker nodes. To simplify this demonstration, you will only create the manger node responsible for deploying the services and handling the application workload. To create a Docker Swarm cluster, run the following command:   docker swarm init You should see the following output confirming that the current node is a manager and relevant instructions to add a worker node to the cluster:  Output Swarm initialized: current node (rpuk92y8wypwqwnv5kqzk5fik) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions. Since we won't be adding a worker node to the Swarm cluster, let's move on to the next section and push the Docker image to DockerHub. If you'd like to see a full demonstration of  running a highly available Docker Swarm setup in production , please see the linked article. 2. Building the application Docker image Inside the application directory, you have a  Dockerfile  which has the following contents: Dockerfile FROM golang:1.20 WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY main.go ./ COPY config/db.go ./config/db.go COPY handler/handlers.go ./handler/handlers.go RUN CGO_ENABLED=0 GOOS=linux go build -o /out/main ./ EXPOSE 8081 # Run ENTRYPOINT ["/out/main"] These commands inside the  Dockerfile  instructs Docker to pull the Go version 1.20 image, then copy the files from our project directory into the Docker image and build an application executable named  main  in the /out directory. It also specifies that the Docker container will listen on port 8081 and that the  /out/main  binary is executed when the container is started. To build the application image, run the following command. Remember to replace the   placeholder with your DockerHub username:   docker build -t /blog . You should observe the following output at the end of the process:  Output . . . Successfully built 222ca8bc81b8 Successfully tagged /blog:latest Now that you have successfully built the application image, log into your DockerHub account from the current session so that you can push the image to your account:   docker login  Output WARNING! Your password will be stored unencrypted in /home//.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded Once login is successful, run the following command to push the image to DockerHub:   docker push /blog Now that you've successfully pushed the image to your DockerHub account, let's move on to the next section and deploy the application to the Swarm cluster. 3. Deploying the application to the Swarm cluster In this section, you will deploy the Go application and the PostgreSQL database to the Swarm cluster. Within the  swarm  directory, you have the two following files: initdb.sql  is for initializing the database state by creating a new table named "posts" when deploying the PostgreSQL database to the Swarm cluster docker-compose.yml  is for creating the PostgreSQL database and the application services to the Swarm cluster Open the  docker-compose.yml  file in your text editor and update the   placeholder to your Docker username: swarm/docker-compose.yml # Use postgres/example user/password credentials version: '3.6' services: db: image: postgres environment: - POSTGRES_DB=blog_db - POSTGRES_USER=blog_user - POSTGRES_PASSWORD=blog_password ports: - '5432:5432' volumes: - ./initdb.sql:/docker-entrypoint-initdb.d/initdb.sql networks: - blog-network blog: image : /blog environment: - POSTGRES_DB=blog_db - POSTGRES_USER=blog_user - POSTGRES_PASSWORD=blog_password - POSTGRES_HOST=db:5432 ports: - '8081:8081' volumes: - .:/app networks: - blog-network networks: blog-network: name: blog-network This file instructs Docker Swarm to create three services called  db ,  blog , and  networks : The  db  service is for creating a PostgreSQL database using the specified configuration. Its state is initialized using the  initdb.sql  file, and it uses the  blog-network  network. The  blog  service creates the blog application using the Docker image you previously pushed to DockerHub. You also need to provide environment variables for this service so that the application can access the PostgreSQL database. This service will also use the  blog-network  network allowing interaction with the  db  service through the host URL:  db:5432 . The  networks  service creates a network named  blog-network  so the two services can communicate via this network. To deploy these three services to the running Swarm cluster, run the following commands in turn:   cd swarm   docker stack deploy --compose-file docker-compose.yml blogapp  Output Creating network blog-network Creating service blogapp_db Creating service blogapp_blog Afterward, run the following command to verify that the services are running:   docker stack services blogapp You should see the following results:  Output ID NAME MODE REPLICAS IMAGE PORTS uptqkx630zpy blogapp_blog replicated 1/1 username/blog:latest *:8081->8081/tcp jgb19lcx32y6 blogapp_db replicated 1/1 postgres:latest *:5432->5432/tcp You should also be able to interact with the application endpoints now. Run the following command to create a new blog post:   curl --location 'http://localhost:8081/blog' \ --header 'Content-Type: application/json' \ --data '{ "body":"this is a great blog" }' Then run the command below to retrieve the newly created post:   curl --location 'http://localhost:8081/blog/1' You should observe the same output as before:  Output {"id":1,"body":"this is a great blog","created_at":"2023-05-13T08:56:26.140815Z","updated_at":"2023-05-13T08:56:26.140815Z"} Now that you've successfully deployed the blog application using Docker Swarm. Let's move on to the next section and learn how to deploy the application using Kubernetes. Deploying the application with Kubernetes To simplify the setup for the Kubernetes cluster, you will use the  minikube tool  which allows you to create an run a local Kubernetes cluster on your machine. Start by following  these instructions  to install the  minikube  binary on your machine, then launch the minikube service by running the following command:   minikube start Afterwards, run the following command to check whether the minikube service is up and running:   minikube kubectl -- get node You should see the output below:  Output NAME STATUS ROLES AGE VERSION minikube Ready control-plane 188d v1.25.3 For ease of use, you should alias the  minukube kubectl --  command to  kubectl  as follows:   alias kubectl="minikube kubectl --" Now that the minikube service is up and running, you will can proceed with deploying the Go application and PostgreSQL database to the Kubernetes cluster. Let's start by deploying the PostgreSQL database in the next section. 1. Deploying the PostgreSQL database to the Kubernetes cluster To deploy the PostgreSQL database to the Kubernetes cluster, you need to: Create a Kubernetes Persistent Volume (Kubernetes PV) so that you don't lose the application data when the Kubernetes cluster is down. Create the Kubernetes Persistent Volume Claim (Kubernetes PVC) to request storage inside the Kubernetes PV. Create the Kubernetes Config Map with initializing SQL query to create a table named  posts  when deploying the PostgreSQL database. Create the Kubernetes Deployment to deploy the PostgreSQL database to the Kubernetes cluster. Create the Kubernetes Service, which allows the blog application to access to the PostgreSQL database. From the current terminal, navigate to the  kubernetes  directory:   cd ../kubernetes Next, create a  postgres/docker-pg-vol/data  directory inside the  kubernetes  directory to store the persistent data:   mkdir postgres/docker-pg-vol/data -p Update the  path  inside the  hostPath  block in the  postgres-volume.yml  file with the absolute path to the persistent data directory. The path should look like this:  /home//betterstack-swarm-kubernetes/kubernetes/postgres/docker-pg-vol/data   nano postgres-volume.yml kubernetes/postgres-volume.yml apiVersion: v1 kind: PersistentVolume metadata: name: postgresql-claim0 labels: type: local spec: storageClassName: manual capacity: storage: 1Gi accessModes: - ReadWriteOnce hostPath: path : "/home//betterstack-swarm-kubernetes/kubernetes/postgres/docker-pg-vol/data" Afterward, run the following command to create the Kubernetes Persistent Volume. If you do not specify the  --namespace  value, Kubernetes will automatically use the  default  namespace.   kubectl apply -f postgres-volume.yml  Output persistentvolume/postgresql-claim0 created Then run the following command to create the Persistent Volume Claim.   kubectl apply -f postgres-pvc.yml  Output persistentvolumeclaim/postgresql-claim0 created To create the configuration map for initializing the SQL query, run the following command:   kubectl apply -f postgres-initdb-config.yml  Output configmap/postgresql-initdb-config created Next, run the following command to deploy the PostgreSQL database:   kubectl apply -f postgres-deployment.yml  Output deployment.apps/postgresql created Finally, create the Kubernetes service for the PostgreSQL database:   kubectl apply -f postgres-service.yml  Output service/postgresql created At this point, the PostgreSQL instance should be up and running. Go ahead and run the command below to confirm:   kubectl get pod You should see the following result:  Output NAME READY STATUS RESTARTS AGE postgresql-655746b9f8-n4dcf 1/1 Running 0 2m36s Run the following command to check for the PostgreSQL service.   kubectl get service You should see the following output:  Output NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 443/TCP 14d postgresql ClusterIP 10.98.157.228 5432/TCP 2s Now that you've successfully deployed the PostgreSQL database to the Kubernetes cluster, let's deploy the blog application next. 2. Deploying the application to the Kubernetes cluster Before you can deploy your application using Kubernetes, you need to create a Docker secret so that Kubernetes can access your DockerHub account to pull the application image. Note that you need to replace the username, password, and email below with your DockerHub account information:   kubectl create secret docker-registry dockerhub-secret \ --docker-server=docker.io \ --docker-username= \ --docker-password= \ --docker-email=  Output secret/dockerhub-secret created Once the secret is created, open the  deployment.yml  file in your text editor and edit it as follows:   nano kubernetes/deployment.yml kubernetes/deployment.yml --- apiVersion: apps/v1 kind: Deployment metadata: name: blogapp spec: replicas: 2 selector: matchLabels: name: blogapp template: metadata: labels: name: blogapp spec: containers: - name: application image : /blog imagePullPolicy: Always envFrom: - secretRef: name: dockerhub-secret env: - name: POSTGRES_DB value: blog_db - name: POSTGRES_USER value: blog_user - name: POSTGRES_PASSWORD value: blog_password - name: POSTGRES_HOST value: postgresql:5432 ports: - containerPort: 8081 Save and close the file, then run the following command to deploy the blog app:   kubectl apply -f deployment.yml  Output deployment.apps/blogapp created Then run the following command to create the Kubernetes service for the blog app, so that you can access the app from outside the Kubernetes cluster.   kubectl apply -f service.yml  Output service/blogapp-service created Run the following command to get the running services inside the Kubernetes cluster.   kubectl get service You should see the following result:   NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE blogapp-service LoadBalancer 10.97.173.87 8081:31983/TCP 5s kubernetes ClusterIP 10.96.0.1 443/TCP 14d postgresql ClusterIP 10.96.38.7 5432/TCP 8m1s Notice that the  EXTERNAL-IP  column for the  blogapp-service  is pending. This is because you are using a custom Kubernetes cluster (minikube) that does not have an integrated load balancer. To workaround this issue, create a minikube tunnel in a new terminal through the command below:   minikube tunnel Then run the  kubectl get service  command again. You should see the updated result with a proper  EXTERNAL-IP  value:  Output NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE blogapp-service LoadBalancer 10.97.173.87 10.97.173.87 8081:31983/TCP 4m40s kubernetes ClusterIP 10.96.0.1 443/TCP 14d postgresql ClusterIP 10.96.38.7 5432/TCP 12m Now that you've deployed the blog application to the Kubernetes cluster, you can access it through its external IP which in the above example is  10.97.173.87 .   curl --location 'http://:8081/blog' \ --header 'Content-Type: application/json' \ --data '{ "body":"this is a great blog" }' Then run the following command to get the blog content with id  1 .   curl --location 'http://:8081/blog/1' You should see the output as:  Output {"id":1,"body":"this is a great blog","created_at":"2023-05-13T08:56:26.140815Z","updated_at":"2023-05-13T08:56:26.140815Z"} And that's how you deploy the blog application to the Kubernetes cluster. Comparing the deployment process between Docker Swarm and Kubernetes, we observe distinct trade-offs. Docker Swarm offers a simpler deployment experience, but it lacks built-in support for persistent data storage. In the event of a Docker Swarm cluster failure or recreation, there is a risk of losing the application data. In contrast, Kubernetes introduces a more intricate deployment process but it offers the advantage of creating persistent data storage, ensuring data durability even during cluster disruptions or recreation. Additionally, Kubernetes provides flexibility in exposing the blog app service as per your requirements by leveraging the Kubernetes Service definition file. Use cases of Kubernetes vs. Docker Swarm Docker Swarm and Kubernetes, despite being container orchestration platforms, possess distinct characteristics that cater to different use cases. Docker Swarm is well-suited for: Beginners in containerization who seek to learn application deployment using containerization techniques. Small to medium-sized applications that require straightforward deployment and management. Users familiar with Docker and prefer a Docker-centric approach to application deployment. Docker Swarm seamlessly integrates with the Docker ecosystem. Applications with a relatively stable user base or predictable traffic patterns. Docker Swarm, although lacking advanced automatic scaling features compared to Kubernetes, can adequately handle such scenarios. Kubernetes should be considered when: You have a comprehensive understanding of Kubernetes components and have acquired proficiency in working with the platform. Kubernetes has a steeper learning curve but offers a comprehensive solution for deploying and managing containerized applications. Your application is complex and demands extensive customization during deployment. Kubernetes excels in managing large-scale, intricate applications, providing advanced management, scalability, and customization capabilities. Fine-grained control and customization options are crucial for your deployment. Automatic scaling is necessary due to your application's growing user base. In summary, Docker Swarm suits simpler deployments and those who prefer a Docker-centric approach, while Kubernetes caters to complex applications, fine-grained control, and automatic scaling needs. Carefully assess your requirements and familiarity with the platforms to determine the most suitable choice for your specific use case. Final thoughts Throughout this article, you've gained insights into the distinctions between Docker Swarm and Kubernetes, and you've also had practical experience deploying a sample Go application using both platforms. To further explore technical articles on cloud-native and containerization technologies, we invite you to visit our  BetterStack community guides . There, you can find additional resources and in-depth information to enhance your understanding of these technologies. Thanks for reading! How to Scale Docker Swarm Horizontally in Production Link: https://betterstack.com/community/guides/scaling-docker/horizontally-scaling-swarm/ Auto Scaling Docker High Availability Cristovao Cordeiro Updated on January 9, 2024 Contents Prerequisites Getting started Step 1 — Checking the current distribution of tasks Step 2 — Scaling out the service to 5 replicas Step 3 — Undoing a scaling operation Step 4 — Scaling in the service to 3 replicas Step 5 — Defining constraints while scaling a service Auto scaling Docker services (optional) Conclusion and next steps With any software service deployment, there might come a time when you need to think about scaling your service due to meet changing demands. Such an action may be triggered by multiple factors including the following: Your service is under heavy load which can ultimately impact its performance. Therefore, you'll want to add more instances of the service throughout the cluster, such that the incoming load can be re-distributed evenly. Your service does not have enough instances running throughout multiple availability zones of your cluster. This means it might be subject to a single point of failure which compromises its availability if one or more nodes in the cluster go down. Therefore, you'll want to add more instances to your service, distributed more equally throughout the cluster to increase its redundancy. Your service is experiencing a low demand from your users, which means you might be hosting and maintaining more instances of your service than what is needed to fulfill the demand. Therefore, you'll want to decrease the number of active service instances to reduce your maintenance efforts and compute costs. In this tutorial, you will learn how to horizontally scale in/out an existing Docker service, and you'll also experiment with scalability constraints and auto scaling solutions. For future reference, when we talk about  Scaling In  and  Scaling Out , we mean the following: Scaling In : removing instances from an existing service. Scaling Out : adding more instances in parallel to spread out the service load.   Centralize & visualize your logs. Query everything with SQL. Explore more   Prerequisites Before proceeding with this article, ensure that you've read and performed all the steps from the previous tutorial on  Setting up Docker Swarm High Availability in Production . This means you should have four replicas of an NGINX service running on a 5-node cluster with three managers and two workers where all managers have been drained:   docker node ls  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq manager-1 Ready Drain Reachable 20.10.18 uspt9qwqnzqwl78gbxc7omja7 * manager-2 Ready Drain Leader 20.10.18 txrdxwuwjpg5jjfer3bcmtc5r manager-3 Ready Drain Reachable 20.10.18 kaq8r9gec4t58yc9oh3dc0r2d worker-1 Ready Active 20.10.18 vk1224zd81xcihgm1iis2703z worker-2 Ready Active 20.10.18 We are currently working on the  manager-2  node, which is the leader and is not schedulable (as it was drained in the previous tutorial). Go ahead and execute the following command to see the active services on the cluster:   docker service ls  Output ID NAME MODE REPLICAS IMAGE PORTS euqfhji2qpco nginx_nginx replicated 4/4 nginx:latest *:8088->80/tcp Currently, there is only one service with four replicas in the entire cluster. For now, ensure that you are connected to the Leader node ( manager-2 in this example). If you do not have access to a Docker cluster as required to follow this tutorial, we recommend you use the online Docker playground .Getting started In this tutorial, we'll mainly use the  docker service  management command to perform all the service inspections and scalability actions. Knowledge of this command is crucial when scaling your services since it allows you to perform the following actions through various subcommands: inspect  all the information about your service, including its previous and current states. list  (or  ls ) all the services running in your cluster. list ( ps ) all the tasks within a service. Each task will correspond to a replica of your service, so this information is critical when scaling in/out. update  a service's specification (i.e., its placement policies, number of replicas, etc.). rollback  a service to its previous specification. scale  one or more services concurrently, in, or out, which is pretty much the same as running a service  update  to change the number of service replicas. To see these subcommands in action, let's take on a 5-step scenario where we'll analyze our current cluster and services, and then scale in and out a service while also applying scalability constraints. Step 1 — Checking the current distribution of tasks Docker services are composed of tasks which are scheduled into the cluster nodes based on the provided placement  preferences  and  constraints . In the previous tutorial, our NGINX service was deployed via a Docker Stack, which was declared with the  nginx.yaml  compose file. Let's confirm this through the command below:   docker stack ls You should observe just one stack which was deployed from our  nginx.yaml  compose file:  Output NAME SERVICES ORCHESTRATOR nginx 1 Swarm Next, list all the services in the  nginx  stack using the command below:   docker stack services nginx  Output ID NAME MODE REPLICAS IMAGE PORTS euqfhji2qpco nginx_nginx replicated 4/4 nginx:latest *:8088->80/tcp The above command produces all the services defined in the  nginx.yaml  file and deployed as part of the  nginx  stack above. In this case, it's the same as running  docker service ls  since there are no other stacks or services in the cluster. It is easy to read through our  nginx.yaml  file to verify that we haven't defined any  placement policies  for this service. Take a look at the  deploy  section of the file: nginx.yaml version: '3.7' networks: nginx: external: false services: # --- NGINX --- nginx: image: nginx:latest ports: - '8088:80' deploy : replicas : 4 update_config : parallelism : 2 order : start - first failure_action : rollback delay : 10s rollback_config : parallelism : 0 order : stop - first restart_policy : condition : any delay : 5s max_attempts : 3 window : 120s healthcheck: test: ["CMD", "service", "nginx", "status"] networks: - nginx We can also confirm this by using  docker service inspect  to get detailed information about the NGINX service. Because this inspection can return an exhaustive amount of information, we can format the output using a Go template. In our case, we know that the service's placement policies are detailed under the service's  Spec->TaskTemplate->Placement  field, so we can format our inspection output by running the following:   docker service inspect nginx_nginx --format '{{json .Spec.TaskTemplate.Placement}}'  Output {"Platforms":[{"Architecture":"amd64","OS":"linux"},{"OS":"linux"},{"OS":"linux"},{"Architecture":"arm64","OS":"linux"},{"Architecture":"386","OS":"linux"},{"Architecture":"mips64le","OS":"linux"},{"Architecture":"ppc64le","OS":"linux"},{"Architecture":"s390x","OS":"linux"}]} As you can see, the service does not have any placement policies. We should expect that Docker is smart enough to distribute the four replicas of this service throughout all schedulable nodes of the cluster as evenly as possible. We have four schedulable nodes in our cluster, so in theory, we should have two NGINX replicas per worker node (since all three manager nodes are drained). Let's confirm this:   docker service ps nginx_nginx # list the tasks of the nginx_nginx service  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikjo7su8ooo6 nginx_nginx.1 nginx:latest worker-2 Running Running 27 hours ago inel921owq9b \_ nginx_nginx.1 nginx:latest manager-2 Shutdown Shutdown 28 hours ago y0y5mvj5n44r nginx_nginx.2 nginx:latest worker-1 Running Running 27 hours ago uk18jo6wwlq6 \_ nginx_nginx.2 nginx:latest manager-3 Shutdown Shutdown 28 hours ago ijbalagf7isy nginx_nginx.3 nginx:latest worker-1 Running Running 27 hours ago drgu007baw8z nginx_nginx.4 nginx:latest worker-2 Running Running 27 hours ago As you can see, both worker nodes  worker-1  and  worker-2  are running two replicas of the NGINX service each. You can also see that the replicas that were running on  manager-2  and  manager-3  were shut down and reassigned to  worker-2  and  worker-1  respectively when both manager nodes were drained in step 8 of the  previous tutorial . Step 2 — Scaling out the service to 5 replicas Let's begin this section by assuming our NGINX service is under a high load. We'll need to add another NGINX instance (aka replica) to cope with the increased demand. We can use the  docker service scale  command for this purpose. We only need to state the service name and the number of desired replicas.   docker service scale nginx_nginx=5  Output nginx_nginx scaled to 5 overall progress: 4 out of 5 tasks 1/5: running [==================================================>] 2/5: starting [============================================> ] 3/5: running [==================================================>] 4/5: running [==================================================>] 5/5: running [==================================================>] Once the service is stable, we'll find a new replica in our service:   docker service ps nginx_nginx  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikjo7su8ooo6 nginx_nginx.1 nginx:latest worker-2 Running Running 28 hours ago inel921owq9b \_ nginx_nginx.1 nginx:latest manager-2 Shutdown Shutdown 29 hours ago y0y5mvj5n44r nginx_nginx.2 nginx:latest worker-1 Running Running 28 hours ago uk18jo6wwlq6 \_ nginx_nginx.2 nginx:latest manager-3 Shutdown Shutdown 29 hours ago ijbalagf7isy nginx_nginx.3 nginx:latest worker-1 Running Running 28 hours ago drgu007baw8z nginx_nginx.4 nginx:latest worker-2 Running Running 28 hours ago 2cd89o4cybmn nginx_nginx.5 nginx:latest worker-2 Running Running 11 seconds ago This new  nginx_nginx.5  task is now running alongside  nginx_nginx.1  and  nginx_nginx.4  in  worker-2 . We could've also added a new NGINX replica by running:   docker service update nginx_nginx --replicas 5 The outcome would've been the same as:   docker service scale nginx_nginx=5 Nonetheless, we recommend always using the  docker service scale  command as it can scale multiple services simultaneously. For example:   docker service scale my_service=3 my_other_service=4 ... On the other hand,  docker service update  can only update one service at a time. Step 3 — Undoing a scaling operation In this step, we will assume that we scaled our service by accident in the previous section and we'd like to revert our changes. As mentioned earlier, the  docker service scale  command is pretty much the same as a  docker service update  that only targets the number of service replicas. What happens under the hood is that the service is taking in a new specification where the number of replicas differs from the current one. So how can we return our service to its previous state? Do we need to remember its entire previous specification by heart? Gladly we don't! Docker is smart enough to keep the service's previous specification within its definition. By running  docker service inspect , we can examine a service's current and previous states:   docker service inspect nginx_nginx  Output [ { "ID": "euqfhji2qpco", . . . "Spec": { "Name": "nginx_nginx", . . . "TaskTemplate": { "ContainerSpec": { "Image": "nginx:latest@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767", "Labels": { "com.docker.stack.namespace": "nginx" }, . . . }, . . . }, "Mode": { "Replicated": { "Replicas": 5 } }, . . . "RollbackConfig": { "Parallelism": 0, "FailureAction": "pause", "Monitor": 5000000000, "MaxFailureRatio": 0, "Order": "stop-first" }, . . . }, "PreviousSpec": { "Name": "nginx_nginx", . . . "TaskTemplate": { "ContainerSpec": { "Image": "nginx:latest@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767", "Labels": { "com.docker.stack.namespace": "nginx" }, . . . }, . . . }, "Mode": { "Replicated": { "Replicas": 4 } }, . . . "RollbackConfig": { "Parallelism": 0, "FailureAction": "pause", "MaxFailureRatio": 0, "Order": "stop-first" }, . . . }, . . . } ] The above command results in a large output (thus truncated), but we can clearly see two top-level keys,  Spec  and  PreviousSpec , which look identical at first, but if you look closely,  PreviousSpec  defines a  Replicated  service with  4  replicas, while  Spec  sets this number to  5  (as expected, given the scale out we've performed in the previous step). Since we have this  PreviousSpec  information, we can determine what our service will look like once we revert to the previous state. But the question now is: how do we undo the last scaling operation? Once again, Docker comes to the rescue! The  docker service  management command has a  rollback  subcommand for these situations. There's not much to know about it, you simply run  docker service rollback  on the desired service, and Docker will automatically update it to its  PreviousSpec  configuration. Go ahead and try it out as shown below:   docker service rollback nginx_nginx  Output nginx_nginx rollback: manually requested rollback overall progress: rolling back update: 4 out of 4 tasks 1/4: running [> ] 2/4: running [> ] 3/4: running [> ] 4/4: running [> ] verify: Service converged rollback: rollback completed If the rollback is successful, we should now see our initial four replicas instead of the five we scaled out to in the previous step:   docker service ps nginx_nginx  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikjo7su8ooo6 nginx_nginx.1 nginx:latest worker-2 Running Running 29 hours ago inel921owq9b \_ nginx_nginx.1 nginx:latest manager-2 Shutdown Shutdown 30 hours ago y0y5mvj5n44r nginx_nginx.2 nginx:latest worker-1 Running Running 29 hours ago uk18jo6wwlq6 \_ nginx_nginx.2 nginx:latest manager-3 Shutdown Shutdown 30 hours ago ijbalagf7isy nginx_nginx.3 nginx:latest worker-1 Running Running 29 hours ago drgu007baw8z nginx_nginx.4 nginx:latest worker-2 Running Running 29 hours ago And there we go, our service is back to where it was in Step 1, running with four replicas. Step 4 — Scaling in the service to 3 replicas Let's now assume we've been able to absorb the high service loads with our previous scale out and are now experiencing low demand for our service. To free up some space and potentially reduce our compute costs, let's shrink our service from four to three replicas:   docker service scale nginx_nginx=3  Output nginx_nginx scaled to 3 overall progress: 3 out of 3 tasks 1/3: running [==================================================>] 2/3: running [==================================================>] 3/3: running [==================================================>] verify: Service converged The command is pretty much the same as in step 2, except for the desired number of replicas. Docker will take care of shutting down one of the replicas, leaving us with the desired number, evenly distributed throughout the cluster once again:   docker service ps nginx_nginx  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikjo7su8ooo6 nginx_nginx.1 nginx:latest worker-2 Running Running 29 hours ago inel921owq9b \_ nginx_nginx.1 nginx:latest manager-2 Shutdown Shutdown 30 hours ago y0y5mvj5n44r nginx_nginx.2 nginx:latest worker-1 Running Running 29 hours ago uk18jo6wwlq6 \_ nginx_nginx.2 nginx:latest manager-3 Shutdown Shutdown 30 hours ago ijbalagf7isy nginx_nginx.3 nginx:latest worker-1 Running Running 29 hours ago Notice that other replicas weren't disturbed by this action — they are still running, uninterruptedly, for 29 hours. Step 5 — Defining constraints while scaling a service Finally, sometimes you might want to ensure that a certain condition is met, even after your service is already running. For example, let's assume we now need to scale out to five NGINX replicas again, but we discovered that our worker nodes are not capable of hosting more than two NGINX replicas concurrently without a performance degradation. In such scenario, we can no longer use  docker service scale  because it does not allow us to specify other configurations besides the number of replicas. This is where the  docker service update  command comes in, as it offers a wide range of options for configuring the new state of our service. We need a new number of replicas, plus one condition (no more than two replicas per node), so we can use the following options:   docker service update --help  Output . . . --constraint-add list Add or update a placement constraint . . . --replicas uint Number of tasks --replicas-max-per-node uint Maximum number of tasks per node (default 0 = unlimited) . . . Go ahead and run the following service update command to scale out to five replicas once more:   docker service update nginx_nginx --replicas 5 --replicas-max-per-node 2  Output nginx_nginx overall progress: 2 out of 5 tasks 1/5: starting [============================================> ] 2/5: running [==================================================>] 3/5: starting [============================================> ] 4/5: running [==================================================>] 5/5: no suitable node (max replicas per node limit exceed; scheduling constrain… You'll observe that Docker will rightfully complain about your decision, because you've restricted each worker to only two replicas which means the fifth one will have no where to go. Since the above command was executed in the foreground, Docker will keep indefinitely, trying to fix this situation. You'll see something like this:  Output nginx_nginx overall progress: 4 out of 5 tasks 1/5: running [==================================================>] 2/5: running [==================================================>] 3/5: running [==================================================>] 4/5: running [==================================================>] 5/5: no suitable node (max replicas per node limit exceed; scheduling constrain… We can  Ctrl+c  to interrupt the process (the service update will continue in the background), and then verify that this new task is pending, waiting for a suitable node to show up:   docker service ps nginx_nginx --no-trunc # use --no-trunc to see the whole output  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikjo7su8ooo6b7wmh4r9uj1lq nginx_nginx.1 nginx:latest@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 worker-2 Running Running 3 days ago inel921owq9bjq4zotkz590dz \_ nginx_nginx.1 nginx:latest@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 manager-2 Shutdown Shutdown 3 days ago y0y5mvj5n44r23b5y548hrvsm nginx_nginx.2 nginx:latest@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 worker-1 Running Running 3 days ago uk18jo6wwlq6qfrv2lr6zk1xz \_ nginx_nginx.2 nginx:latest@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 manager-3 Shutdown Shutdown 3 days ago ijbalagf7isyu1drhm0hu8z95 nginx_nginx.3 nginx:latest@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 worker-1 Running Running 3 days ago lqa493b94iu8lpmc8yqtu4jd1 nginx_nginx.4 nginx:latest@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 worker-2 Running Running 4 minutes ago txc8rjd3xpojvl7ckz8zgohx9 nginx_nginx.5 nginx:latest@sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1 Running Pending 5 minutes ago "no suitable node (3 nodes not available for new tasks; max replicas per node limit exceed)" To move the highlighted  Pending  task above to  Running , you can take either of the following actions: Redefine the scheduling constraints by allowing the cluster to run more than two replicas per node (as defined in the  docker service update  command above). Horizontally scaling the cluster by provisioning additional worker nodes (this will be covered in the next tutorial). Auto scaling Docker services (optional) When talking about scaling container services, "auto scaling" often comes up. Now, the reason why this is a bonus (and optional) step, is because  Docker doesn't really offer any auto scaling mechanisms  (unlike  Kubernetes ). Nonetheless, you might be wondering how to introduce some level of automation to help scale in and out Docker services. Let's face it, there is no right answer for this, nor there is any "one size fits all" solution that can help you. Because we cannot auto scale directly with Docker, we must resort to workarounds that might involve third-party tools, custom scripts, etc. There are some tools out there (such as  Orbiter ) that are designed specifically to fill this gap in Docker. But depending on the use case and auto scaling rules, you could build a custom Docker auto scaler with just a few lines of Bash and a  cronjob ! Our NGINX service from above currently has five replicas. Regardless of their state and placement policies, let's say we simply want to  add  and  delete  service replicas every time a worker joins or leaves the Docker Swarm cluster, such that we always have two replicas per worker. Start by building a script to monitor the cluster size and scale the service when necessary. As a superuser, create a new file called "/opt/autoscaler.sh" and open it with your favorite text editor:   sudo nano /opt/autoscaler.sh Paste the following text into the file: /opt/autoscaler.sh #!/bin/sh service_name=nginx_nginx # 1 ## let's see how many workers we have, by listing and counting all ## node IDs corresponding to the workers in the cluster num_workers=$(docker node ls --filter role=worker -q | wc -l) # 2 ## then, let's infer the current number of replicas our service has num_replicas=$(docker service inspect $service_name --format '{{json .Spec.Mode.Replicated.Replicas}}') # 3 ## let's then assess how many replicas we should have (2 per node) let "target_replicas = 2*$num_workers" # 4 ## and finally, let's compare the target replicas ## with the ones we currently have, and if they are different ## we scale the service to the number of target_replicas if [[ $num_replicas -ne $target_replicas ]] then echo "Autoscaling Docker service ${service_name} to ${target_replicas} replicas" docker service scale $service_name=$target_replicas -d fi ## else, there's nothing to do since we already have ## the desired amount of replicas The above file defines a simplistic auto scaling rule, but applicable nonetheless. Next, you need to run this script periodically via a cronjob (say once every hour). To do that, execute the following command to open the crontab config file in your text editor:   crontab -e Add the following line at the end of the file, then save and close it.   0 * * * * sh /opt/autoscaler.sh With this setup in place, the  /opt/autoscaler.sh  script will be executed once per hour by the system's  cron  daemon. While this autoscaling infrastructure isn't production quality, it is enough to bootstrap your understanding of how to work around the autoscaling limitations in Docker. Conclusion and next steps In this tutorial, you learned about scaling a Docker Swarm service in and out, and how to undo a scaling operation. You also learned how to inspect a service so that you can make educated decisions about your scaling constraints. Finally, we touched base on auto scaling, giving you some tips on how to automate the scaling operations for your service. In a future tutorial, we'll learn about horizontally scaling the cluster itself, such that a suitable node shows up to serve the pending task from step five. This article was contributed by guest author  Cristovao Cordeiro , a Docker certified Engineering Manager at Canonical. He's an expert in Containers and ex-CERN engineer with 9+ years of experience in Cloud and Edge computing. Setting up Docker Swarm High Availability in Production Link: https://betterstack.com/community/guides/scaling-docker/ha-docker-swarm/ Docker High Availability Ayooluwa Isaiah Updated on January 14, 2026 Contents Prerequisites Explaining Docker Swarm terminology Docker Swarm requirements for high availability Step 1 — Installing Docker Step 2 — Executing the Docker command without sudo Step 3 — Initializing the Swarm Cluster Step 4 — Adding worker nodes to the cluster Step 5 — Adding manager nodes to the cluster Step 6 — Draining a node on the swarm Step 7 — Deploying a Highly Available NGINX service Step 8 — Draining the other manager nodes Step 9 — Testing the failover mechanism Monitoring your Docker Swarm cluster Final thoughts Docker Swarm  is a container orchestration tool that makes it easy to manage and scale your existing Docker infrastructure. It consists of a pool of Docker hosts that run in Swarm mode with some nodes acting as managers, workers, or both. Using Docker Swarm mode to manage your Docker containers brings the following  benefits : It allows you to incrementally apply updates with zero downtime. It increases application resilience to outages by reconciling any differences between the actual state and your expressed desired state. It eases the process of scaling your applications since you only need to define the desired number of replicas in the cluster. It is built into the  docker  CLI, so you don't need additional software to get up and running. It enables multi-host networking such that containers deployed on different nodes can communicate with each other easily. In this tutorial, you will learn key concepts in Docker Swarm and set up a highly available Swarm cluster that is resilient to failures. You will also learn some best practices and recommendations to ensure that your Swarm setup is fault tolerant. Prerequisites Before proceeding with this tutorial, ensure that you have access to five Ubuntu 22.04 servers. This is necessary to demonstrate a highly available set up, although it is also possible to run Docker Swarm on a single machine. You also need to configure each server with a user that has administrative privileges. The following ports must also be available on each server for communication purposes between the nodes. On Ubuntu 22.04, they are open by default: TCP port 2377 for cluster management communications, TCP and UDP port 7946 for communication among nodes, TCP and UDP port 4789 for overlay network traffic.   Centralize & visualize your logs. Query everything with SQL. Explore more   Explaining Docker Swarm terminology Before proceeding with this tutorial, let's examine some terms and definitions in Docker Swarm so that you have enough understanding of what each one means when they are used in this article and in other Docker Swarm resources. Node : refers to an instance of the Docker engine in the Swarm cluster. Manager nodes : they are tasked with handling orchestration and cluster management functions, and dispatching incoming tasks to worker nodes. They can also act as worker nodes unless placed in Drain mode (recommended). Leader : this is a specific manager node that is elected to perform orchestration tasks and management/maintenance operations by all the manager nodes in the cluster using the  Raft Consensus Algorithm . Worker nodes : are Docker instances whose sole purpose is to receive and execute Swarm tasks from manager nodes. Swarm task : refers to a Docker container and the commands that run inside the container. Once a task is assigned to a node, it can run or fail but it cannot be transferred to a different node. Swarm service : this is the mechanism for defining tasks that should be executed on a node. It involves specifying the container image and commands that should run inside the container. Drain : means that new tasks are no longer assigned to a node, and existing tasks are reassigned to other available nodes. Docker Swarm requirements for high availability A highly available Docker Swarm setup ensures that if a node fails, services on the failed node are re-provisioned and assigned to other available nodes in the cluster. A Docker Swarm setup that consists of one or two manager nodes is not considered highly available because any incident will cause operations on the cluster to be interrupted. Therefore the minimum number of manager nodes in a highly available Swarm cluster should be three. The table below shows the number of failures a Swarm cluster can tolerate depending on the number of manager nodes in the cluster: Manager Nodes Failures tolerated 1 0 2 0 3 1 4 1 5 2 6 2 7 3 As you can see, having an even number of manager nodes does not help with failure tolerance, so you should always maintain an odd number of manager nodes. Fault tolerance improves as you add more manager nodes, but Docker recommends no more than seven managers so that performance is not negatively impacted since each node must acknowledge proposals to update the state of the cluster. You should also distribute your manager nodes in separate locations so they are not affected by the same outage. If they run on the same server, a hardware problem could cause them all to go down. The high availability Swarm cluster that you will be set up in this tutorial will therefore exhibit the following characteristics: 5 total nodes (2 workers and 3 managers) with each one running on a separate server. 2 worker nodes ( worker-1  and  worker-2 ). 3 manager nodes ( manager-1 ,  manager-2 , and  manager-3 ). Step 1 — Installing Docker In this step, you will install Docker on all five Ubuntu servers. Therefore, execute all the commands below (and in step 2) on all five servers. If your host offers a snapshot feature, you may be able to run the commands on a single server and use that server as a base for the other four instances. Let's start by installing the latest version of the Docker Engine (20.10.18 at the time of writing). Go ahead and update the package information list from all configured sources on your system:   sudo apt update Afterward, install the following packages to allow  apt  use packages over HTTPS:   sudo apt install apt-transport-https ca-certificates curl software-properties-common Next, add the GPG key for the official Docker repository to the server:   curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg Once the GPG key is added, include the official Docker repository in the server's apt sources list.   echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null Finally, update apt once again and install the Docker Engine:   sudo apt update   sudo apt install docker-ce Once the relevant packages are installed, you check the status of the  docker  service using the command below:   sudo systemctl status docker If everything goes well, you should observe that the container engine is active and running on your server:  Output ● docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2022-09-21 10:18:15 UTC; 58s ago TriggeredBy: ● docker.socket Docs: https://docs.docker.com Main PID: 25355 (dockerd) Tasks: 7 Memory: 22.2M CPU: 346ms CGroup: /system.slice/docker.service └─25355 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock In the next step, we will add the current user to the  docker  group so that you can use the  docker  command without escalating to administrative privileges (using  sudo ) which can lead to security issues. Step 2 — Executing the Docker command without sudo By default, the  docker  command can only be executed by the root user or any user in the  docker  group (auto created on installation). If you execute a  docker  command without prefixing it with  sudo  or running it through a user that belongs to the  docker  group, you will get a permission error that looks like this:  Output Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json": dial unix /var/run/docker.sock: connect: permission denied As mentioned earlier, using  sudo  with  docker  is a security risk, so the solution to the above error is to add the relevant user to the  docker  group which can be achieved through the command below:   sudo usermod -aG docker ${USER} Next, run the following command and enter the user's password when prompted for the changes to take effect:   su - ${USER} You should now be able to run  docker  commands without prefixing them with  sudo . For example, when you run the command below:   docker ps You should observe the following output:   CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES Before proceeding to the next step, ensure that all the commands in step 1 and step 2 have been executed on all five servers. Step 3 — Initializing the Swarm Cluster At this point, each of your five Docker instances are acting as separate hosts and not as part of a Swarm cluster. Therefore, in this step, we will initialize the Swarm cluster on the  manager-1  server and add the hosts to the cluster accordingly. Start by logging into one of the Ubuntu servers ( manager-1 ), and retrieve the private IP address of the machine using the following command:   hostname -I | awk '{print $1}'  Output Copy the IP address to your clipboard and replace the   placeholder in the command below to initialize Swarm mode:   docker swarm init --advertise-addr  Output Swarm initialized: current node (9r83zto8qpqiazt6slxfkjypq) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token : To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions. The command above enables Swarm mode on the node and configures it as the first manager of the cluster. Ensure to copy the entire command to your clipboard (and replace the placeholders) as it will be utilized in the next section. You can view the current state of the Swarm using the command below:   docker info  Output . . . Swarm: active NodeID: 9r83zto8qpqiazt6slxfkjypq Is Manager: true ClusterID: q6laywz9u8xlis9wlkbzap8i0 Managers: 1 Nodes: 1 . . . You can also use the command below to view information regarding the nodes on the cluster:   docker node ls  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq * manager-1 Ready Active Leader 20.10.18 The  *  next to the node ID indicates that you're currently connected to this node. Here's the meaning of the other values: Ready : the node is recognized by the manager and can participate in the cluster. Active : the node is used as a worker also to run Docker containers. Leader : the node is a manager node and is also the cluster's leader. In the next section of this tutorial, we will add two workers to our cluster bringing the total nodes to three. Step 4 — Adding worker nodes to the cluster Adding worker modes to a cluster can be done by running the command copied from step 3 above (the  docker swarm init  output) on the  worker-1  and  worker-2  servers:   docker swarm join --token :  Output This node joined a swarm as a worker. If you forgot to copy the  join  command for workers, use the command below on the  manager-1  server to retrieve it:   docker swarm join-token worker  Output To add a worker to this swarm, run the following command: docker swarm join --token : After executing the  join  command on each worker node, you should be able to see the updated list of Swarm nodes when you run the command below on your  manager-1  server:   docker node ls  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq * manager-1 Ready Active Leader 20.10.18 kaq8r9gec4t58yc9oh3dc0r2d worker-1 Ready Active 20.10.18 vk1224zd81xcihgm1iis2703z worker-2 Ready Active 20.10.18 The empty  MANAGER  column for the  worker-1  and  worker-2  nodes identifies them as worker nodes. Note that you can promote a worker node to a manager mode by using the command below:   docker node promote  Output Node promoted to a manager in the swarm. Step 5 — Adding manager nodes to the cluster In this section, you will add the remaining two nodes to the cluster as managers. This time, you need to retrieve the  join  command for manager nodes by running the command below on the  manager-1  server:   docker swarm join-token manager Output:  Output To add a manager to this swarm, run the following command: docker swarm join --token : Copy the generated command above and execute it on each additional manager nodes ( manager-2  and  manager-3 ) as shown below:   docker swarm join --token :  Output This node joined a swarm as a manager. Finally, verify the status of the cluster nodes using the following command:   docker node ls  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq * manager-1 Ready Active Leader 20.10.18 uspt9qwqnzqwl78gbxc7omja7 manager-2 Ready Active Reachable 20.10.18 txrdxwuwjpg5jjfer3bcmtc5r manager-3 Ready Active Reachable 20.10.18 kaq8r9gec4t58yc9oh3dc0r2d worker-1 Ready Active 20.10.18 vk1224zd81xcihgm1iis2703z worker-2 Ready Active 20.10.18 Note that you can also demote a manager node to a worker as follows:   docker node demote  Output Manager demoted in the swarm. Step 6 — Draining a node on the swarm At the moment, all five nodes in the swarm cluster are running with  Active  availability. This means they are all available to accept new tasks from the swarm manager (including the leader). If you want to avoid scheduling tasks on a node, you need to  drain  it such that no new tasks are assigned to it, and existing tasks are stopped and relaunched on a replica node with  Active  availability. In this section, you will drain the manager node so that it is no longer able to receive new tasks, which should help to improve its performance since no resources will be allocated towards running Docker containers. Before you can drain a node, you need to figure out ID of the node to be drained using the following command on the  manager-1  server:   docker node ls Copy the ID of the  Leader  node:  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq * manager-1 Ready Active Leader 20.10.18 . . . Next, update the node availability using the below command:   docker node update --availability drain  Output When you run  docker node ls  once more, you should observe that the  AVAILABILITY  of the  Leader  node has been changed to  Drain :   docker node ls Output:  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq * manager-1 Ready Drain Leader 20.10.18 uspt9qwqnzqwl78gbxc7omja7 manager-2 Ready Active Reachable 20.10.18 txrdxwuwjpg5jjfer3bcmtc5r manager-3 Ready Active Reachable 20.10.18 kaq8r9gec4t58yc9oh3dc0r2d worker-1 Ready Active 20.10.18 vk1224zd81xcihgm1iis2703z worker-2 Ready Active 20.10.18 You can also use the  docker node inspect  command to check the availability of a node:   docker node inspect --pretty  Output ID: Hostname: manager-1 Joined at: 2022-09-21 11:07:08.730840341 +0000 utc Status: State: Ready Availability: Drain Address: 116.203.21.130 Manager Status: Address: Raft Status: Reachable Leader: Yes . . . If you change your mind about draining a node, you can return it to an active state by executing the following command:  Output docker node update --availability active Step 7 — Deploying a Highly Available NGINX service In this section, you will deploy a service to the running cluster using a High Availability Swarm configuration. We'll be utilizing the  official NGINX docker image  for demonstration purposes, but you can use any Docker image you want. Start by creating the compose file for the  nginx  service on the  manager-1  server with all the necessary configurations for High Availability mode.   nano nginx.yaml nginx.yaml version: '3.7' networks: nginx: external: false services: # --- NGINX --- nginx: image: nginx:latest ports: - '8088:80' deploy: replicas: 4 update_config: parallelism: 2 order: start-first failure_action: rollback delay: 10s rollback_config: parallelism: 0 order: stop-first restart_policy: condition: any delay: 5s max_attempts: 3 window: 120s healthcheck: test: ["CMD", "service", "nginx", "status"] networks: - nginx The above file configures an  nginx  service with four replicas. Updates to the containers will be carried out in batches (two at a time) with a wait time of 10 seconds before updating the next batch. If an update failure is detected, it will roll back to the previous configuration. Please see the  Compose file reference  for more information. Go ahead and deploy the NGINX stack on the  manager-1  node using the command below:   docker stack deploy -c nginx.yaml nginx  Output Creating network nginx_nginx Creating service nginx_nginx Once you deploy the stack, you will be able to see a list of running services on the cluster using the command below:   docker service ls  Output ID NAME MODE REPLICAS IMAGE PORTS xh24wj31z4ml nginx_nginx replicated 4/4 nginx:latest *:8088->80/tcp You can also see which nodes are running the service:   docker service ps  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS inel921owq9b nginx_nginx.1 nginx:latest manager-2 Running Running 2 minutes ago uk18jo6wwlq6 nginx_nginx.2 nginx:latest manager-3 Running Running 2 minutes ago ijbalagf7isy nginx_nginx.3 nginx:latest worker-1 Running Running 2 minutes ago drgu007baw8z nginx_nginx.4 nginx:latest worker-2 Running Running 2 minutes ago Each replica runs on the four  Active  nodes in this case. The  DESIRED STATE  and  CURRENT STATE  columns lets you determine the tasks are running according to the service definition. If you want to see details about the container for a task, run  docker ps  on the relevant node. For example, on  manager-3 :   docker ps  Output CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 62e396145307 nginx:latest "/docker-entrypoint.…" 7 minutes ago Up 7 minutes (healthy) 80/tcp nginx_nginx.2.uk18jo6wwlq6qfrv2lr6zk1xz Monitor your Docker Swarm cluster with Better Stack While Docker Swarm handles container orchestration,  Better Stack  provides comprehensive monitoring for your distributed services. Track container health, collect logs from all nodes, and get alerted when services fail with infrastructure monitoring that checks your cluster every 30 seconds. Predictable pricing and up to 30x cheaper than Datadog.  Start free in minutes.     Step 8 — Draining the other manager nodes As mentioned earlier, manager nodes should ideally be only responsible for management-related tasks. Currently, two of the three manager nodes are running replicas of the NGINX service which could hamper management operations under certain conditions. To prevent such interference, it is best to mark them as unavailable for running tasks by draining them as follows:   docker node update --availability drain Once you've done so for both the  manager-2  and  manager-3  nodes, you'll observe that their  AVAILABILITY  has been updated to drain:   docker node ls  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq * manager-1 Ready Drain Leader 20.10.18 uspt9qwqnzqwl78gbxc7omja7 manager-2 Ready Drain Reachable 20.10.18 txrdxwuwjpg5jjfer3bcmtc5r manager-3 Ready Drain Reachable 20.10.18 kaq8r9gec4t58yc9oh3dc0r2d worker-1 Ready Active 20.10.18 vk1224zd81xcihgm1iis2703z worker-2 Ready Active 20.10.18 The NGINX replicas that were running on both manager nodes are subsequently stopped and reassigned to each worker node. You can confirm that four replicas are still running using the command below:   docker service ls  Output ID NAME MODE REPLICAS IMAGE PORTS xh24wj31z4ml nginx_nginx replicated 4/4 nginx:latest *:8088->80/tcp Now, confirm which nodes are running each replica through the command below:   docker service ps  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikjo7su8ooo6 nginx_nginx.1 nginx:latest worker-2 Running Running 19 minutes ago inel921owq9b \_ nginx_nginx.1 nginx:latest manager-2 Shutdown Shutdown 19 minutes ago y0y5mvj5n44r nginx_nginx.2 nginx:latest worker-1 Running Running 19 minutes ago uk18jo6wwlq6 \_ nginx_nginx.2 nginx:latest manager-3 Shutdown Shutdown 19 minutes ago ijbalagf7isy nginx_nginx.3 nginx:latest worker-1 Running Running about an hour ago drgu007baw8z nginx_nginx.4 nginx:latest worker-2 Running Running about an hour ago As you can see, the  manager-2  and  manager-3  tasks were shut down 19 minutes ago and subsequently reassigned to  worker-2  and  worker-1  respectively. Your manager nodes are now only responsible for cluster management activities and maintaining high availability of the cluster. Step 9 — Testing the failover mechanism Before concluding this tutorial, let's test the availability of our cluster by causing the current leader ( manager-1 ) to fail. Once this happens, the other managers should detect the failure and elect a new leader. You can cause the  manager-1  node to become unavailable by stopping the  docker  service on the server:   sudo systemctl stop docker Afterward, run the  docker node ls  command on any of the other manager nodes:   docker node ls  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq manager-1 Ready Drain Unreachable 20.10.18 uspt9qwqnzqwl78gbxc7omja7 manager-2 Ready Drain Leader 20.10.18 txrdxwuwjpg5jjfer3bcmtc5r * manager-3 Ready Drain Reachable 20.10.18 kaq8r9gec4t58yc9oh3dc0r2d worker-1 Ready Active 20.10.18 vk1224zd81xcihgm1iis2703z worker-2 Ready Active 20.10.18 Notice that  manager-1  is deemed unreachable and  manager-2  has been elected the new leader. If you check the NGINX service status, you'll observe that the replicas running on the worker nodes were all restarted afterward:   docker service ps  Output ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikjo7su8ooo6 nginx_nginx.1 nginx:latest worker-2 Running Running 3 minutes ago inel921owq9b \_ nginx_nginx.1 nginx:latest manager-2 Shutdown Shutdown 34 minutes ago y0y5mvj5n44r nginx_nginx.2 nginx:latest worker-1 Running Running 3 minutes ago uk18jo6wwlq6 \_ nginx_nginx.2 nginx:latest manager-3 Shutdown Shutdown 34 minutes ago ijbalagf7isy nginx_nginx.3 nginx:latest worker-1 Running Running 3 minutes ago drgu007baw8z nginx_nginx.4 nginx:latest worker-2 Running Running 3 minutes ago When you start the  docker  service on  manager-1  once again, it will be marked as  Reachable , but  manager-2  will remain the  Leader  of the cluster.   sudo systemctl start docker   docker node ls  Output ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 9r83zto8qpqiazt6slxfkjypq * manager-1 Ready Drain Reachable 20.10.18 uspt9qwqnzqwl78gbxc7omja7 manager-2 Ready Drain Leader 20.10.18 txrdxwuwjpg5jjfer3bcmtc5r manager-3 Ready Drain Reachable 20.10.18 kaq8r9gec4t58yc9oh3dc0r2d worker-1 Ready Active 20.10.18 vk1224zd81xcihgm1iis2703z worker-2 Ready Active 20.10.18 Monitoring your Docker Swarm cluster You've set up a highly available Docker Swarm cluster that can withstand node failures and automatically redistribute workloads. However, high availability means nothing without proper monitoring to detect issues before they impact your services.     Better Stack  provides unified monitoring for your entire Swarm cluster: Infrastructure monitoring checks all your nodes every 30 seconds from global locations Collect logs from all containers across manager and worker nodes in one place Track container health, resource usage, and service availability Get instant alerts via email, SMS, or phone when nodes become unreachable Monitor NGINX and other services with automatic health checks Create status pages to communicate cluster status to users Incident management tools coordinate your team during outages Instead of manually checking  docker service ps  and  docker node ls , Better Stack gives you real-time visibility into your entire cluster. When a manager node fails like in Step 9, you'll get alerted immediately rather than discovering it later. The platform combines infrastructure monitoring, log management, and incident response in one place, eliminating the need to piece together multiple monitoring tools for your Swarm cluster. If you want comprehensive monitoring for your Docker Swarm setup, check out  Better Stack . Final thoughts A highly available setup is one of the essential requirements for any production system, but building such systems used to be a tedious and complex task. As demonstrated in this tutorial, using Docker and Docker Swarm makes this task much easier and also takes fewer resources when compared to other technologies (such as Kubernetes) used to accomplish the same type of high availability setup. There's a lot more pertaining to Docker Swarm (security, scaling, secrets management, etc) that cannot be covered in one tutorial, so ensure to check out the  official Swarm guide  and the rest of our  scaling docker tutorial series . If you would like to read more on Docker, feel free to also explore our  Docker logging guide . Thanks for reading! Swarmpit Docker Link: https://github.com/swarmpit/swarmpit#installation         https://swarmpit.io/   Lightweight mobile-friendly Docker Swarm management UI ⚠️  Status:  This UI is in maintenance mode. Click  here  for details.         Swarmpit provides simple and easy to use interface for your Docker Swarm cluster. You can manage your stacks, services, secrets, volumes, networks etc. After linking your Docker Hub account or custom registry, private repositories can be easily deployed on Swarm. Best of all, you can share this management console securely with your whole team. Swarmpit doesn't compromise your privacy as it is completely self-hosted and will never gather any metrics or other data from you. More details about future and past releases can be found in  ROADMAP.md Installation The only dependency for Swarmpit deployment is Docker with Swarm initialized, we are supporting Docker 1.13 and newer. Linux hosts on x86 and ARM architectures are supported as well. Package installer Installer is your guide to setup Swarmpit platform. For more details see the  installer Stable version Deploy our current milestone version docker run -it --rm \ --name swarmpit-installer \ --volume /var/run/docker.sock:/var/run/docker.sock \ swarmpit/install:1.9 Edge version Deploy latest version for the brave and true docker run -it --rm \ --name swarmpit-installer \ --volume /var/run/docker.sock:/var/run/docker.sock \ swarmpit/install:edge Manual installation Deploy Swarmpit by using a compose file from our git repo with branch of corresponding version. git clone https://github.com/swarmpit/swarmpit -b master docker stack deploy -c swarmpit/docker-compose.yml swarmpit For ARM based cluster use custom compose file. git clone https://github.com/swarmpit/swarmpit -b master docker stack deploy -c swarmpit/docker-compose.arm.yml swarmpit This stack  is a composition of 4 services: app - Swarmpit agent  - Swarmpit agent db - CouchDB (Application data) influxdb - InfluxDB (Cluster statistics) Feel free to edit the stackfile to change an application port and we strongly recommend to specify following volumes: db-data influxdb-data to shared-volume driver type of your choice. Alternatively, you can link db service to the specific node by using  constraint . Swarmpit is published on port  888  by default. Environment Variables Refer to following  document User Configuration By default Swarmpit offers you to configure first user using web interface. If you want to automate this process, you can use docker config to provide users.yaml file. Refer to following  document  for details. User Types Refer to following  document Development Swarmpit is written purely in Clojure and utilizes React on front-end. CouchDB is used to persist application data & InfluxDB for cluster statistics. Everything about building, issue reporting and setting up development environment can be found in  CONTRIBUTING.md Demo Deploys Swarmpit to play-with-docker sandbox. Please wait few moments till application is up and running before accessing port 888. Initialization might take a few seconds.