Build a Sing Box Server with Docker and Ansible
4 min read ·
Last modified
I now use ChatGPT virtually everywhere, and it has almost entirely replaced search engines in my workflow. However, I rencently noticed that ChatGPT occasionally “acts less intelligent” - for example, suddenly losing web browsing capabilities or providing significantly lower-quality responses. After some quick research, I discovered that OpenAI has added new restrictions: if a session request comes from an IP address deemed insufficiently “clean,” it silently switches to a less capable model. This renders commercial VPN services unusable, as fiding a node with a sufficiently “clean” IP on such platforms is nearly impossible. Self-hosting a private proxy/VPN now seems like the only viable solution.
For machine selection, ZgoCloud’s (AFF link here) Osaka AMD Performances instance suits my need well. While its IP reputation isn’t as pristine as their Los Angles instances (which use residential IPs compared to Osaka’s broadcast IPs), the streaming-unblocking capabilities remain remarkably robust. The impressively low latency between Japanese servers and mainland China even allows me to repurpose it for additional uses, such as serving as a RustDesk relay server in certain remote access scenarios.
On the technical front, while I remain a dedicated NixOS advocate, the stark reality is that few providers offer native NixOS installations The prospect of manually SSH-ing into every new machine to deploy via nixos-infect
script during each migration proved too burdensome. This led me to compromise with Docker Compose as my deployment strategy. Yet even this alternative requires tedious manual configurations - from Docker installation to BBR optimization and TCP Fast Open tuning - which ultimately drove me to implement Ansible for orchestrating these repetitive tasks through automated playbooks.
First, let’s define the Ansible inventory. I actually have another Los Angeles-based instance leased from BageVM (AFF link here), though its unoptimized network routes result in subpar performance. In terms of service accessibility, this machine only reliably prevents ChatGPT’s “model degradation” on mobile clients, while web browser access still frequently encounters intelligence downgrades (but at $2.6/month for 4TB bandwidth, expectations should remain modest). Nevertheless, since it’s already provisioned, I’ll include it in the configuration to avoid resource waste. These two instances can share identical configurations, requiring only differentiation through the country_flag variable. The main variable serves to accelerate deployment workflows - since the BageVM instance doesn’t require Serenity subscription link generation (adequately handled by the ZgoCloud node), we can safely bypass that process.
vps:
hosts:
bagevm.fayash.me:
country_flag: 🇺🇸
zgo-osaka.fayash.me:
main: true
country_flag: 🇯🇵
Next comes the playbook configuration. For Docker installation, we must use the official CE version rather than Debian’s docker.io
and docker-compose
packages. The docker-compose
from Debian’s repositories is essentially a wrapper script around Docker rather than a proper plugin, making it incompatible with Ansible’s community.docker.docker_compose_v2
module. Considering this VPS might serve multiple purposes, we’ll implement Caddy both as a reverse proxy server and for automated SSL certificate management through its native ACME integration.
Given the requirement to host the entire project on GitHub, all proxy-related cryptographic keys must be secured through Ansible Vault encryption. This necessitates creating a dedicated vars/secrets.yml
file following Ansible’s security best practices.
The docker-compose.yml
file requires templating to accommodate functional differences across VPS instances with distinct roles.
- name: Deploy
hosts: vps
become: true
vars_files:
- vars/secrets.yml
tasks:
- name: Install Docker
block:
- name: Install prequisites
apt:
name:
- ca-certificates
- curl
- gpg
state: present
update_cache: true
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/debian/gpg
state: present
- name: Add Docker repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
state: present
- name: Install Docker
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
state: present
update_cache: true
- name: Enable BBR
block:
- name: Load tcp_bbr module
modprobe:
name: tcp_bbr
persistent: present
- name: Enable BBR
sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
state: present
loop:
- name: net.core.default_qdisc
value: fq
- name: net.ipv4.tcp_congestion_control
value: bbr
- name: Enable TFO
sysctl:
name: net.ipv4.tcp_fastopen
value: 3
state: present
reload: true
- name: Upload files
block:
- name: Install rsync
apt:
name: rsync
state: present
update_cache: true
- name: Upload files
synchronize:
src: ./docker-compose/
dest: /opt/docker-compose/
rsync_opts:
- "--compress"
- "--delete"
- name: Templating
template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
loop:
- name: Generate docker-compose
src: templates/docker-compose.yml.j2
dest: /opt/docker-compose/docker-compose.yml
- name: Generate Caddyfile
src: templates/Caddyfile.j2
dest: /opt/docker-compose/caddy/Caddyfile
- name: Generate sing-box config
src: templates/sing-box.json.j2
dest: /opt/docker-compose/sing-box/config.json
loop_control:
label: "{{ item.name }}"
- name: Generate serenity config
template:
src: templates/serenity-per-user.json.j2
dest: /opt/docker-compose/serenity/{{ item.name }}.json
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"
when: main | default(false)
- name: Start Docker Compose
community.docker.docker_compose_v2:
project_src: /opt/docker-compose/
recreate: always
remove_orphans: true
This is the core configuration workflow. The remaining files are intentionally straghtforward, with the entire project repository hosted on GitHub. You can easily adapt it to your environment by simply modifying var_files/secrets.yml
and inventory.yml
.