A Step-by-Step Guide to Deploying Fullstack Angular + Node.js/Spring Boot Applications on a VPS Using Dokku (Ubuntu 24.04 LTS)
This guide provides a detailed walkthrough for deploying a fullstack application with an Angular frontend and a Node.js or Spring Boot backend on a VPS running Ubuntu 24.04 LTS, using Dokku.
0. Dockerization: Choosing the Right Virtualization Platform — LXC vs. KVM
When considering Dockerization, it’s crucial to choose the right virtualization technology for your Virtual Private Server (VPS). Two popular options are KVM (Kernel-based Virtual Machine) and LXC (Linux Containers). Each has its strengths and weaknesses, which can significantly impact your Docker environment. However, for beginners, it’s often recommended to start with a Managed Virtual Server (MVS), as it requires less configuration and is simpler to Dockerize.
KVM (Kernel-based Virtual Machine)
KVM is a full virtualization solution integrated into the Linux kernel. It allows multiple independent operating systems to run on a single physical machine, with each virtual machine (VM) having direct access to hardware resources like CPU and RAM. This setup provides near-native performance, making KVM an excellent choice when performance is a priority.
Advantages of KVM:
- Full Isolation: KVM provides complete isolation between VMs, enhancing security and stability. This is particularly beneficial when running different operating systems, such as Windows, macOS, and various Linux distributions.
- Versatile OS Support: KVM supports a wide range of operating systems, making it ideal for environments where different OS versions need to be tested or deployed.
- High Performance: The direct access to hardware resources ensures that VMs run at near-native speeds, which is critical for resource-intensive applications.
Using Docker with KVM:
KVM’s strong isolation and versatility make it a solid foundation for running Docker. Docker can be installed within a KVM-based VM, providing a robust environment where Docker containers can operate with high security and performance.
LXC (Linux Containers)
LXC is a lightweight virtualization solution that leverages the Linux kernel’s cgroups and namespace features. Unlike KVM, LXC uses containers that share the host system’s kernel, resulting in faster startup times and lower resource consumption.
Advantages of LXC:
- Faster Startup: Containers in LXC start almost instantly, making it ideal for scenarios where quick deployment and scaling are crucial.
- Lower Resource Usage: Since LXC containers share the host’s kernel, they require significantly fewer resources compared to full VMs, leading to higher efficiency in resource utilization.
- Simplified Administration: LXC’s lightweight nature simplifies management, making it easier to handle large-scale deployments.
Limitations of LXC:
- Reduced Isolation: LXC containers are less isolated than KVM VMs, which can pose security risks, especially in multi-tenant environments.
- Kernel Dependency: LXC containers rely on the host’s kernel, limiting the flexibility to run different operating systems.
Docker vs. LXC:
While both LXC and Docker involve containerization, they serve different purposes. Docker is designed specifically for creating, deploying, and managing applications in isolated containers, with a focus on ease of use and scalability. LXC, on the other hand, is more geared towards creating virtualized environments with a focus on system-level management.
It’s important to note that Docker cannot be run directly within LXC containers due to conflicts in their containerization approaches. However, Docker can be run within KVM-based VMs, providing a more secure and isolated environment for Docker containers.
Which to Choose for Dockerization?
- Use KVM if you need strong isolation, high performance, and the ability to run multiple operating systems. KVM is ideal for running Docker in scenarios where security and resource allocation are critical.
- Use LXC if you prioritize fast deployment, lower resource consumption, and simpler management. LXC is better suited for environments where containers need to be quickly spun up and down with minimal overhead, but be cautious of the reduced isolation.
Recommendation for Beginners: MVS Server
For those new to Dockerization, starting with a Managed Virtual Server (MVS) is often the best choice. An MVS server comes pre-configured with many of the necessary settings, reducing the amount of manual configuration required. This makes it easier to set up Docker and get your containers running without dealing with the complexities of configuring a VPS from scratch. Additionally, managed services often provide better support and easier scalability, allowing beginners to focus more on developing and deploying their applications rather than managing the underlying infrastructure.
In summary, the choice between KVM, LXC, and an MVS server depends on your specific needs and experience level. For beginners, an MVS server offers simplicity and ease of use, making Dockerization more accessible. For more advanced users, KVM provides strong isolation and performance, while LXC offers a lightweight alternative for less demanding use cases where speed and efficiency are the primary concerns.
1. Before Installing Dokku
How to Resolve Issues During Dokku Installation — Before Installing Dokku
When attempting to install Dokku, you might encounter an error indicating that certain dependencies are unavailable or cannot be installed. This issue can prevent Dokku from being successfully set up. The following steps can help you troubleshoot and resolve this problem:
Check and Update Repositories
First, ensure that all necessary repositories are added and update your package list:
sudo apt-get update
3. Install Docker (if it is not installed already)
Dokku relies on Docker, so it’s crucial to have Docker installed. If Docker is not already installed on your system, you can manually install it:
sudo apt-get install docker.io
After Docker is installed, you may also need to install the required Docker plugins:
sudo apt-get install docker-compose-plugin docker-buildx-plugin
For more detailed instructions on installing Dokku, refer to the Dokku Installation Guide. Additionally, for deploying applications, you can consult the Application Deployment Documentation.
2. Installing Dokku on Ubuntu 24.04 LTS
To begin, you can install Dokku on your Ubuntu 24.04 LTS server. Dokku is an open-source platform-as-a-service (PaaS) that allows you to manage your applications on a VPS, offering a self-hosted alternative to services like Heroku.
After installing Dokku, you’ll be guided through setting up your SSH key and configuring the basic settings. Follow the on-screen instructions to ensure everything is properly configured.
Here’s how to install Dokku:
Download and Install Dokku
First, download the installation script and run it:
wget -NP . https://dokku.com/install/v0.34.9/bootstrap.sh
sudo DOKKU_TAG=v0.34.9 bash bootstrap.sh
During the installation, Dokku will automatically install Nginx and Docker, which are essential components for running your applications.
3. Set Up Virtualhost Routing (Optional)
If you want to use virtualhost routing (e.g., my-app => my-app.dokku.me
), set a global domain for your server:
dokku domains:set-global <main domain of the server>
4. Add SSH Key for Deployment
To enable deployments, you need to add an SSH key for the user. Pass the public SSH key as shown below:
echo 'CONTENTS_OF_ID_RSA_PUB_FILE' | dokku ssh-keys:add admin
By following these steps, you’ll have a fully functional Dokku instance ready for deploying your applications.
3. Generate a GitHub Personal Access Token and Clone Your Repository
To clone your private GitHub repository, you’ll need a personal access token. This token replaces your password in GitHub’s HTTPS authentication.
Generate a Personal Access Token:
- Go to your GitHub account.
- Navigate to Settings > Developer settings > Personal access tokens > Tokens (classic) > Generate new token.
- Select the necessary scopes, such as
repo
, and generate the token.
Clone Your Repository:
Use the generated token to clone your repository. Replace <your_personal_token>
and YourRepository
with your actual token and repository name:
git clone https://<your_personal_token>@github.com/YourUsername/YourRepository.git
cd YourRepository
3. Installing Essential Dokku Plugins
To enhance Dokku’s core functionality, you can install various plugins that extend its capabilities. Here are some essential plugins you should consider installing:
Database Plugins
These plugins allow Dokku to manage databases. They are crucial for enabling database support within your applications. You can find the official plugins here: Dokku Official Plugins.
Let’s Encrypt Plugin
The Let’s Encrypt plugin automates the process of setting up SSL certificates, making it easy to secure your applications with HTTPS.
Install the Let’s Encrypt plugin by executing the following command:
sudo dokku plugin:install https://<your_personal_token>@github.com/dokku/dokku-letsencrypt
These plugins will significantly enhance your Dokku environment, providing vital functionality for managing databases and securing your applications with SSL.
4. Configure Dockerfile or Procfile
Before deploying, ensure that your backend has a Dockerfile or Procfile configured. Dokku uses these files to understand how to build and run your application. It must be in the root directory.
For Node.js, a simple Procfile might look like this:
web: npm start
If you’re using Docker, make sure your Dockerfile is properly configured to build and run your application.
An example frontend Angular app can be run with this Procfile:
web: npx serve -s dist/matching-game-angular
A simple frontend HTML website can also be dockerized and run with Nginx or Apache2 if necessary. The content of the Dockerfile:
# Base image
FROM nginx:alpine
# Copy static files to the nginx html directory
COPY . /usr/share/nginx/html
# Expose the default port
EXPOSE 80
This is an example for a fullstack application that stores questions and users in a MySQL database, with a Node.js (Express.js) backend and an Angular frontend.
Frontend Procfile
web: npx serve -s dist/<APP-NAME>
Backend Procfile
web: node src/wait-for-db.js
wait-for-db.js — the environment variables were added by Dokku
const mysql = require('mysql2/promise');
const logger = require('./logger/logger');
const app = require('./server');
const port = process.env.PORT || 3000;
const seedDatabase = require('./seed/seeder');
const dbConfig = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: 3306
};
async function checkDatabaseConnection() {
let retries = 5;
while (retries) {
try {
const connection = await mysql.createConnection(dbConfig);
await connection end();
logger.info('Database connection established. Seeding database...');
await seedDatabase();
app.listen(port, () => {
logger.info(`App listening at http://localhost:${port}`);
});
break;
} catch (err) {
logger.error('Unable to connect to the database. Retrying in 5 seconds...');
logger.error(err);
retries -= 1;
await new Promise(res => setTimeout(res, 5000));
}
}
if (!retries) {
logger.error('Failed to connect to the database after multiple attempts.');
process.exit(1);
}
}
checkDatabaseConnection();
5. Create Dokku Apps for Frontend and Backend
Next, create separate Dokku apps for your frontend and backend. This step ensures that Dokku treats them as independent applications, allowing you to manage them separately.
Backend (API) app creation
dokku apps:create my-backend-app
Frontend app creation
dokku apps:create my-frontend-app
6. Set Domains for Backend and Frontend
You need to configure custom domains for both the backend and frontend. This setup allows your applications to be accessible through the appropriate URLs.
Backend domain settings (with and without www)
dokku domains:add my-backend-app api.myapp.example.com
dokku domains:add my-backend-app www.api.myapp.example.com
Frontend domain settings (with and without www)
dokku domains:add my-frontend-app myapp.example.com
dokku domains:add my-frontend-app www.myapp.example.com
Domain remove in Dokku — if needed:
dokku domains:remove <app-name> <domain-name>
7. Set Environment Variables
Environment variables are crucial for configuring various settings of your applications, such as API endpoints, database credentials, and CORS settings.
CORS settings for the AP
dokku config:set my-backend-app CORS_ORIGIN="https://myapp.example.com, https://www.myapp.example.com"
API base URL settings for the frontend
dokku config:set my-frontend-app API_BASE_URL="https://api.myapp.example.com, https://www.api.myapp.example.com"
8. Enable SSL with Let’s Encrypt
Ensuring the security of your web applications is crucial, and using SSL certificates is a key part of that. Let’s Encrypt provides free SSL certificates, and you can easily enable SSL for both your backend and frontend applications with Dokku.
Follow these steps to enable SSL:
- Set Up Your Email for Let’s Encrypt (once)
If you encounter an error stating that a certificate cannot be requested without an email address, you need to set your email address. Here’s how you can do it:
dokku letsencrypt:set --global email user@example.com
This command sets the global email address for Let’s Encrypt notifications. After setting the email, you can re-run the SSL enabling commands if necessary.
2. Enable SSL for the Backend Application
To enable SSL for your backend application, use the following commands:
dokku letsencrypt:enable my-backend-app
dokku letsencrypt:cron-job --add
3. Enable SSL for the Frontend Application
Similarly, enable SSL for your frontend application:
dokku letsencrypt:enable my-frontend-app
dokku letsencrypt:cron-job --add
Handling Permission Issues (first time)
If you encounter a “permission denied while trying to connect to the Docker daemon socket” error, it usually indicates that the user running the commands is not part of the Docker group or lacks the necessary permissions. Here’s how to resolve it:
- Check User Group Membership
Verify if your user is part of the Docker group:
groups
If the Docker group is not listed, you need to add your user to the Docker group.
2. Add User to the Docker Group
Add the current user to the Docker group:
sudo usermod -aG docker $USER
3. Apply the Changes
Log out and log back in for the group changes to take effect, or run the following command to activate the changes immediately:
newgrp docker
9. Add MySQL Database and Link It to Backend
If your application requires a database, you can create a MySQL database and link it to your backend. Note: We use MySQL 5.7 because it’s more compatible with older CPUs that might not support newer instruction sets used by MySQL 8.0 or later.
Create a MySQL database
dokku mysql:create myapp-db --image-version 5.7
Link the database to the backend app
dokku mysql:link myapp-db my-backend-app
or
dokku mysql:info myapp-db
10. Retrieve and Configure Database Credentials
After creating and linking the database, you need to retrieve the database connection information and set it as environment variables in your backend application.
Retrieve Database Information:
Use the following command to display the MySQL service information:
dokku mysql:info myapp-db
This command will provide details such as the database host, username, and password.
Set Environment Variables:
Using the information retrieved, set the environment variables required for your backend to connect to the database:
dokku config:set my-backend-app DB_HOST=<your_database_host>
dokku config:set my-backend-app DB_NAME=<your_database_name>
dokku config:set my-backend-app DB_USER=<your_database_user>
dokku config:set my-backend-app DB_PASSWORD=<your_database_password>
dokku config:set my-backend-app DATABASE_URL=mysql://<your_database_user>:<your_database_password>@<your_database_host>:3306/<your_database_name>
An example of fine configured app:
user@server:~$ dokku config app-name
=====> app-name env vars
CORS_ORIGIN: https://example.com, https://www.example.com
DATABASE_URL: mysql://mysql:password@database-host:3306/db_name
DB_HOST: database-host
DB_NAME: db_name
DB_PASSWORD: password
DB_USER: mysql
DOKKU_APP_RESTORE: 1
DOKKU_APP_TYPE: herokuish
DOKKU_PROXY_PORT: 80
DOKKU_PROXY_SSL_PORT: 443
GIT_REV: commit_hash
11. Deploy Applications from Git Repository
Now that your Dokku environment is fully set up, you can deploy your applications directly from a Git repository. Since your repository includes both frontend and backend code, you’ll use git subtree
to specify which part of the repository should be deployed to the respective Dokku app.
If your Dokku server’s SSH connection does not use the default port 22, but instead uses a different port (e.g., 62222), you need to specify this port during the git push
command. You can do this by either modifying the remote repository URL or by directly specifying the port in the command.
1. Modify the Remote Repository URL with the Correct Port
If you prefer to permanently set the correct port for your remote repository, follow these steps:
Remove the Old Remote Repository (if needed)
First, remove the existing remote repository entry:
git remote remove my-dokku-app
Add the Remote Repository with the Correct Port
Next, add the remote repository again, this time with the correct port:
git remote add my-dokku-app ssh://dokku@your-server-ip:62222:my-dokku-app
2. Deploying with the Correct Port
Once the remote repository is set up with the correct port, you can deploy your application using the git push
command:
git push my-dokku-app main:master
3. Alternative Method: Specifying the Port in the Command
If you don’t want to modify the remote repository settings, you can specify the port directly in the git push
command:
git push ssh://dokku@your-server-ip:62222/my-dokku-app main:master
By following these steps, you can ensure that you are using the correct port to connect to your Dokku server and successfully deploy your application.
Now that everything is set up, you can deploy your applications directly from the Git repository. Since your repository contains both frontend and backend code, you’ll use git subtree
to specify which part of the repository should be deployed to which Dokku app.
Deploy frontend and backend from the same repo
For example, suppose you have a full-stack application in a single repository. In the root directory, there’s a Node.js (Express.js) backend located in the backend
folder and an Angular frontend in the frontend
folder. We will deploy these in two separate repositories because it is safer.
Backend Deployment:
git remote add dokku-backend dokku@your-server-ip:my-backend-app
git subtree push --prefix backend dokku-backend main
If the SSH runs on the default 22 port:
git push dokku-backend main:master
If the port number of your SSH or the branch name of the source code is not the default:
git subtree push --prefix backend "ssh://dokku@your-server-ip:<portnumber>/dokku-backend" <branch name e.g.: main>
Frontend Deployment:
git remote add dokku-frontend dokku@your-server-ip:my-frontend-app
git subtree push --prefix frontend dokku-frontend main
If the port number of your SSH or the branch name of the source code is not the default:
git subtree push --prefix frontend "ssh://dokku@your-server-ip:<portnumber>/dokku-frontend" <branch name e.g.: main>
When deploying applications using Dokku, it’s common to encounter various challenges or issues that require troubleshooting. Fortunately, Dokku provides an extensive troubleshooting guide that covers a wide range of potential problems and their solutions. This resource is invaluable for diagnosing deployment errors, resolving connectivity issues, and ensuring that your applications run smoothly in production. Whether you’re dealing with container crashes, build failures, or environment configuration issues, the guide offers step-by-step instructions to help you get your deployment back on track efficiently.
Setting Docker Restart Policies for Dokku Applications
In a production environment, ensuring that your applications are resilient to failures and automatically restart when needed is crucial. Docker provides a feature called restart policies, which dictate how a container should behave when it exits. For applications deployed using Dokku, you can leverage this feature to automatically restart your apps in case of failures or system reboots. In this chapter, we’ll discuss how to set up Docker restart policies both during the initial deployment of Dokku applications and for existing apps, ensuring they remain highly available.
Setting Docker Restart Policies During Initial Deployment
When deploying a new application on Dokku, it’s important to consider how the application will handle failures or system reboots right from the start. By setting a Docker restart policy during the initial deployment, you can ensure that your application will automatically restart if it crashes or if the server reboots. This proactive step helps maintain high availability and reduces the need for manual intervention.
Step 1: Set the Docker Restart Policy During Deployment
Before or immediately after deploying your application, you can set the Docker restart policy to ensure that the application container restarts automatically when needed. Use the following Dokku command to set the restart policy:
dokku docker-options:add <app-name> deploy "--restart=always"
Replace <app-name>
with the name of your Dokku application.
Step 3: Verify the Restart Policy
After setting the restart policy, it’s a good practice to verify that it has been correctly applied. You can do this by inspecting the Docker container associated with your Dokku application:
docker inspect --format '{{ .HostConfig.RestartPolicy.Name }}' $(dokku ps:inspect <app-name> --format "{{ .ID }}")
This command should return always
, confirming that the container is configured to restart automatically.
Why Set the Restart Policy During Deployment?
Setting the Docker restart policy during the initial deployment ensures that your application is prepared to handle failures and reboots from the very beginning. This approach is especially important in production environments where downtime can have significant impacts. By configuring the restart policy upfront, you avoid potential issues and ensure that your application remains available to users with minimal disruptions.
Incorporating this step into your deployment process is a simple yet effective way to enhance the resilience and reliability of your applications, contributing to a smoother and more predictable operation of your services.
Automating Docker Restart Policies for Existing Dokku Applications
If you already have applications deployed on Dokku, you can configure Docker’s restart policy to ensure that these applications automatically restart if they go down. Below, we’ll walk through the process of setting up this policy for all existing Dokku apps using a simple Bash script.
Step 1: Listing All Existing Dokku Applications
First, you need to retrieve a list of all currently deployed Dokku applications. Dokku provides the apps:list
command for this purpose. However, by default, this command includes a header (=== App Name
) that we need to filter out. Here’s how you can do it:
apps=$(dokku apps:list | tail -n +2)
The tail -n +2
command skips the first line, ensuring you only get the names of the applications.
Step 2: Loop Through Each Application and Apply the Restart Policy
Once you have the list of applications, you can loop through each one and set the Docker restart policy using the docker-options:add
command provided by Dokku:
for app in $apps; do
echo "Setting restart policy for $app..."
dokku docker-options:add $app deploy "--restart=always"
done
This loop will iterate over each application and configure Docker to always restart the container whenever it stops, regardless of the reason.
Step 3: Running the Script
To automate this process for all your existing applications, you can combine the commands into a Bash script. Here’s the complete script:
#!/bin/bash
# List all Dokku apps
apps=$(dokku apps:list | tail -n +2)
# Loop through each app and set the restart policy
for app in $apps; do
echo "Setting restart policy for $app..."
dokku docker-options:add $app deploy "--restart=always"
done
echo "Restart policy has been set for all Dokku apps."
- Create the Script: Save the above script in a file, for example,
set-restart-policy.sh
. - Make the Script Executable: Grant execute permissions to the script:
chmod +x set-restart-policy.sh
3. Run the Script: Execute the script to apply the restart policy to all your Dokku apps:
./set-restart-policy.sh
Step 4: Verifying the Configuration
After running the script, you may want to verify that the restart policy has been correctly applied to your applications. You can do this by inspecting the Docker container associated with a particular Dokku app:
docker inspect --format '{{ .HostConfig.RestartPolicy.Name }}' $(dokku ps:inspect <app-name> --format "{{ .ID }}")
Replace <app-name>
with the name of your Dokku application. If everything is configured correctly, this command should return always
, indicating that the restart policy is active.
Why This Matters
Setting up a Docker restart policy is a crucial step in ensuring the availability and resilience of your applications. In a production environment, unexpected issues such as crashes, system reboots, or failures can occur. By configuring your Dokku applications to automatically restart, you minimize downtime and ensure that your services remain accessible to users with minimal intervention.
Additionally, this setup is particularly useful in scenarios where you’re managing multiple applications, as it automates recovery processes that would otherwise require manual intervention.
Conclusion:
By following these steps, you’ve successfully deployed a fullstack Angular + Node.js/Spring Boot application on a VPS using Dokku. The separation of the frontend and backend applications, along with the integration of environment variables, SSL, and database configuration, ensures that your deployment is robust, secure, and scalable.