Building an Open-Source Bastion Host Based on next-terminal
This article was last updated 287 days ago. The information within may have evolved or changed; if anything is outdated or broken, please feel free to leave a message in the comments section.
Article Abstract
To meet the requirements of deploying an open-source bastion host, this solution reuses an existing MariaDB database, builds a custom bridge network, and deploys the Guacamole remote connection gateway and the next-terminal terminal management component to achieve Web-based remote desktop access. The solution focuses on solving database reuse, inter-container network communication, and public network access configuration issues. It uses a non-default bridge network instead of the `--link` method to improve the reliability of inter-container communication, ultimately enabling RDP/VNC/SSH access via a browser, which simplifies the deployment process compared to professional bastion host solutions. The accompanying reverse proxy configuration scheme can adapt to different network environments, including scenarios without a public IP.
Qwen3-14B · 2026-06-18

Preface

Actually, I didn't want to write this article at first, as I've always been too lazy to write about topics that yield tons of search results online... But then I thought, taking this as an opportunity to document my process of building an application (mainly including the reuse of an existing database) from start to finish, so that in the future when I need it, I can just say "refer to my xx article" and be even lazier. That's why this article came to be.

Like some other applications (nextcloud, wordpress, chevereto, shlink, jumpserver, etc.), next-terminal requires a database. Below is the official explanation of the environment variables:

image.png


Speaking of which, I still recommend that when deploying an application, you should try to look at the official documentation (if there is no official website, you can check the relevant pages on GitHub or Docker Hub). Sometimes online tutorials may not be comprehensive or suitable for your environment, so try to develop a good habit of reading official documentation.


I generally use MariaDB. Although MySQL is written here, in most cases, MariaDB can be considered compatible with MySQL (I have encountered incompatibility, but only once so far). Some applications also distinguish between MySQL and MariaDB as different database types (such as shlink). For this deployment, I will still use MariaDB instead of MySQL for my database.

Initialize the database

As I mentioned in previous articles, I only directly use existing databases and do not spin up new databases with Docker Compose at every turn. Therefore, when encountering applications that rely on a MariaDB database, I must first manually initialize the database. For specific steps, please refer to the following article:Tips and Tricks Series: Creating a New Empty Database and Granting Permissions to Corresponding Users. Follow the steps in the article to create a database named "next-terminal", as well as a database user named "next-terminal" (please define the password yourself) and grant the user permission to access the database.

If you don't have an existing database yet, you need to create a new database container and then initialize the database. Refer to the following command:

docker run --name=mariadb01 -d --restart=always --network=public-net \
  -p 3306:3306 \
  -v /docker/mariadb/db:/var/lib/mysql \
  -e MARIADB_ROOT_PASSWORD=yourpassword \
  mariadb:10.11

Create bridge

Since a bridge named public-net will be used below, it needs to be created in advance:

docker network create public-net

Deploying Guacamole

-p 3306:3306 \

-v /docker/mariadb/db:/var/lib/mysql \

docker run --name=guacd -d --restart=always --network=public-net \
  -v /docker/next-terminal/data:/usr/local/next-terminal/data \
  dushixiang/guacd:latest

mariadb:10.11Guacamole is a remote desktop gateway (which can also be called a jump server, because without it, you would really need a separate jump server) that allows us to directly access RDP, VNC, and SSH via a web browser.The command to create a new container is as follows:

docker run --name=guacd -d --restart=always --network=public-net \


-v /docker/next-terminal/data:/usr/local/next-terminal/data \


Deploying next-terminal

docker run --name next-terminal -d --restart=always --network=public-net \
  -p 8088:8088 \
  -v /docker/next-terminal/data:/usr/local/next-terminal/data \
  -v /etc/localtime:/etc/localtime \
  -e DB=mysql \
  -e MYSQL_HOSTNAME=mariadb01 \
  -e MYSQL_PORT=3306 \
  -e MYSQL_USERNAME=nextterminal \
  -e MYSQL_PASSWORD=yourpassword \
  -e MYSQL_DATABASE=nextterminal \
  -e GUACD_HOSTNAME=guacd \
  -e GUACD_PORT=4822 \
  dushixiang/next-terminal:latest

Parameter explanation:
The directory mounted by the above `-v` parameter /docker/next-terminal/data
is the mount directory for the next-terminal container to be installed below `--network=public-net` This parameter must be specified for both the guacd container and the next-terminal container, otherwise the connection between next-terminal and guacd can only rely on `--link`Guacamole is a remote desktop gateway (which can also be called a jump server, because without it, you would really need a separate jump server) that allows us to directly access RDP, VNC, and SSH via a web browser.Containers within the same non-default bridge can communicate directly using their container names. `--link` is not a good way, as you might get confused later when there are many such associations. Therefore, it is recommended to develop a good habit: use `--network` (or `--net`) to add new containers to the same bridge network, or plan ahead and place them in different bridges so that they can communicate directly using container names when needed.docker run --name next-terminal -d --restart=always --network=public-net \-p 8088:8088 \
-v /docker/next-terminal/data:/usr/local/next-terminal/data \ -v /etc/localtime:/etc/localtime \
-e DB=mysql \ -e MYSQL_HOSTNAME=mariadb01 \
-e MYSQL_PORT=3306 \ -e MYSQL_USERNAME=nextterminal \-e MYSQL_PASSWORD=yourpassword \-e MYSQL_DATABASE=nextterminal \
-e GUACD_HOSTNAME=guacd \ -e GUACD_PORT=4822 \
dushixiang/next-terminal:latest -p 8088:8088
Maps port 8088 of the container to port 8088 of the host. The first 8088 is the host port, which you can modify according to your actual situation. -v /docker/next-terminal/data:/usr/local/next-terminal/data
Mounts the host's to the container's
/usr/local/next-terminal/data directory
-v /etc/localtime:/etc/localtime Synchronizes the host's local time to Docker

-e DB=mysql \http://宿主机ip:8088Specifies the database type as mysql

image.png

-e MYSQL_HOSTNAME=mariadb01
image.png

All done.

I won't go into detail about the subsequent steps, as there are many tutorials online. To sum it up in one sentence: add assets, and then click "Access" to connect:

image.png

image.png

image.png

To be honest, this is much simpler than jumpserver. I'm still in a daze about jumpserver:(, mainly because I haven't played with a professional bastion host before and am unfamiliar with some concepts. Moreover, for a home data center, those high-end features are indeed useless, so there is no motivation to study them. Originally, I was planning to write about jumpserver, but after setting it up, I was confused by some of the concepts inside, so I finally compromised and switched to next-terminal.

Regarding jumpserver, if you are interested, you can also try it yourself. In addition to initializing the database, you also need to deploy redis in advance. If it is not deployed, refer to the following command:

docker run --name redis -d --restart=always --network=public-net -p 6379:6379 redis

I installed the all-in-one version, and the command is as follows:

docker run --name jumpserver -d --restart=always --network=public-net \
  -v /docker/jumpserver/core/data:/opt/jumpserver/data \
  -v /docker/jumpserver/koko/data:/opt/koko/data \
  -v /docker/jumpserver/lion/data:/opt/lion/data \
  -p 9040:80 \
  -p 2222:2222 \
  -p 30000-30100:30000-30100 \
  -e SECRET_KEY=xxxxxxxx \
  -e BOOTSTRAP_TOKEN=xxxxxxxx \
  -e LOG_LEVEL=ERROR \
  -e DB_HOST=mariaDB01 \
  -e DB_PORT=3306 \
  -e DB_USER=jumpserver \
  -e DB_PASSWORD=yourpassowrd \
  -e DB_NAME=jumpserver \
  -e REDIS_HOST=redis \
  -e REDIS_PORT=6379 \
  --privileged=true \
  jumpserver/jms_all:v3.4.3

There are many specific tutorials online, you can just search for them, I won't bother with it.

Configure public network access

If you want to publish to the public network, you need to choose the most suitable publishing method based on your actual environment and the reverse proxy you use. You can refer to several of my previous articles:
1、Docker Series: Building Your Own Reverse Proxy Based on NPM Using Docker
2、Linux Panel Series: Configuring Reverse Proxy and Publishing Using Non-443 Ports
3、Home Data Center Series: Getting Cloudflare for Free via Domestic ICP-Filed Cloud Hosts to Achieve Fast Access to Domestic Sites from Abroad
4、Home Data Center Series: Getting Cloudflare for Free via Home Broadband Without Public IP to Achieve Fast Website Building (Universal)

Among them, the 1st and 2nd methods are suitable for environments with a public IP but without a legal 443 port (home broadband, non-filed cloud hosts), where non-standard ports need to be added after the URL (if using Cloudflare to build a website, no port needs to be added, but you need to customize the origin server port, which you can refer to:Home Data Center Series: Solving the Problem of Having a Public IP but No Legal 80 or 443 Ports for Website Building via Cloudflare's Origin Rules). The 3rd method is suitable for cloud hosts with ICP filing, and the 4th method is suitable for all environments (including environments without a public IP), which is also my recommended method (regardless of whether your environment has a public IP, because this method does not require running HTTPS traffic directly on the public network).

View Related Categories · 3 Matches
📌 Content Structure Prompt:
This content is part of the following knowledge map, where you can view the complete content path: Blog Knowledge Map。
📎 Related Articles
Share this article
The blog content is original, please indicate the source when reposting! The RSS address of the blog is:https://blog.tangwudi.com/feed, welcome to subscribe; if needed, you can join theTelegram Groupto discuss questions together.
No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗ
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
Kaomoji
Emoji
Little Dinosaur
Flower!
Previous
Next
       

👋 Welcome to “Wudi's Personal Blog”

Here, long-term exploration is mainly carried out around the following directions:

🧱 Personal Digital Infrastructure and Blog System Construction
☁️ Cloudflare and Network Architecture Practice
🧠 AI and Knowledge System Exploration
🛡️ Network Security and Access Optimization
🎵 Music and Sound Cognition
👁️ Cognitive Perspectives and Worldviews