23 min read

Set Up Doudizhu and Add Remote Access with Localtonet

Install and verify the Python, Tornado, and MySQL Doudizhu server locally, then expose its HTTP service securely with Localtonet.

Self-Hosting Tutorials ยท svzdev/doudizhu ยท HTTP Tunneling ยท Localtonet ยท 2026

Prepare svzdev/doudizhu locally before adding remote browser access

This guide covers the svzdev/doudizhu Python, Tornado, MySQL, and Phaser project, not a similarly named standalone HTML game. We explain how to pin the observed repository revision, initialize its database, follow the documented backend startup sequence, and validate the browser application before forwarding it with Localtonet. Recent history records a TypeScript client migration that the older root README does not explain, so frontend preparation remains a required verification gate. The revision below is an observed commit, not a successfully tested release, and this guide does not claim otherwise.

๐Ÿ”’ Keep MySQL private ๐ŸŒ Forward the verified HTTP service โšก Validate assets and gameplay, not just the landing page
A local Doudizhu server connected to a remote browser through a Localtonet HTTP tunnel.
Localtonet routes remote HTTP traffic to the running game server. This explanatory illustration is not evidence of a tested revision.

Identify the repository and pin an immutable revision

This article concerns svzdev/doudizhu. Its README describes a Python and Tornado backend, MySQL persistence, and a Phaser frontend. It documents Python 3.8 or newer, MySQL 5.7 or newer, the DATABASE_URI environment variable, startup with python3 app.py, and the address http://127.0.0.1:8080. These are repository documentation claims, not a compatibility certification for every newer software version.

The README still instructs readers to clone mailgyc/doudizhu. We use the explicitly identified svzdev/doudizhu repository instead. Instructions for other Dou Dizhu projects, including opening a single HTML file or starting an unrelated Node application, do not apply here.

The supplied commit history identifies 7092ea77cb1273c3726ecd8b9be03d9f489eb8ed as the observed master revision. Unlike a branch name, this full commit identifier is an immutable source reference. It does not establish successful installation, security review, or remote gameplay.

Publication gate: pinned, but not yet tested

No clean-environment execution results accompany the supplied evidence, and no installation or browser test has been performed for this revision in preparing this revision proposal. Before publication as a reproducible tutorial, an editor must inspect the pinned client, resolve its build requirements, and execute the complete database, backend, browser, tunnel, restart, and shutdown sequence. Do not describe the commands below as a tested end-to-end installation until that work is complete.

For a fresh review checkout, use the actual observed SHA rather than a placeholder or a movable branch:

git clone https://github.com/svzdev/doudizhu.git
cd doudizhu
git checkout --detach 7092ea77cb1273c3726ecd8b9be03d9f489eb8ed
git rev-parse HEAD
git status --short

The reported SHA should match exactly. A detached checkout is appropriate for inspecting a fixed revision; it is not an error. Record any local changes separately so later troubleshooting does not silently mix original source with deployment patches.

Review the same revision's README, schema.sql, requirements.txt, and server directory. If testing selects a different revision, update every setup-critical reference and rerun the checks. Pinning source alone does not necessarily pin transitive dependencies or the host environment.

Understand the local and remote request paths

Doudizhu architecture with a Tornado application running on Python and using MySQL.
The application layer handles browser requests and accesses MySQL separately.

This is not a standalone HTML page. The browser-facing resources belong to a running application, and persistence depends on MySQL. The reviewed template directory includes index.html and poker.html, but their presence does not prove that every frontend asset required by the current client is ready to serve.

๐Ÿ Python and Tornado The documented backend starts from the server directory and exposes a local HTTP endpoint on port 8080.
๐Ÿ—„๏ธ MySQL persistence The schema creates the ddz database and its user and record tables. Database access stays behind the application.
๐ŸŽฎ Phaser client The repository includes a client directory, with recent history reporting a JavaScript-to-TypeScript migration.
๐ŸŒ Localtonet HTTP tunnel Our client connects outbound to a relay and forwards a public HTTPS address to the configured local web service.

In the remote path, a browser reaches the Localtonet public endpoint, which forwards traffic to Tornado. Tornado accesses MySQL as needed. Players do not need a database connection, and a tunnel does not replace the backend, compile the client, initialize tables, or grant application permissions.

Standard HTTP tunneling is not a VPN. It publishes the selected service, not a private network membership. It also does not establish who is authorized to use the game. Authentication, room restrictions, and any multiplayer connection mechanism must be inspected and tested in the pinned application.

Prepare the host and resolve client requirements

Use a host where you can install Python dependencies, run the backend, and administer or reach a MySQL server. The repository evidence does not prescribe an operating-system package manager, service supervisor, or production deployment platform. Use supported installation methods for your operating system rather than assuming a particular Linux distribution.

RequirementDocumented or observedRequired check
PythonREADME says 3.8+Record the actual interpreter version and dependency installation result.
MySQLREADME says 5.7+Test the actual schema syntax on the selected server version.
SourceFull observed SHA supplied aboveConfirm the checkout and review local modifications.
Client preparationClient directory and TypeScript migrationInspect build metadata, tool versions, outputs, and server integration.
Local endpointREADME gives 127.0.0.1:8080Verify page, assets, connections, and database-backed actions.
Localtonet deviceMust reach the application targetTest connectivity from the actual client device.

These POSIX-shell commands inspect tools and tracked source without installing packages:

python3 --version
python3 -m pip --version
mysql --version
git rev-parse HEAD
git ls-tree -r --name-only HEAD client
git show HEAD:requirements.txt
git show HEAD:schema.sql

mysql --version reports the client version, not necessarily the connected server version. Query SELECT VERSION(); after connecting to record the server actually used.

An isolated Python environment reduces conflicts with system packages. Where the standard venv module is available, a general POSIX setup is:

python3 -m venv .venv
. .venv/bin/activate
python3 -m pip --version

This is general Python environment preparation, not a repository-prescribed tool choice. Availability and activation differ by operating system. Use the same environment for dependency installation and application startup.

Do not skip the TypeScript client inspection

History records a Phaser 3 client migration from JavaScript to TypeScript on July 22, 2026. The root README still presents only the older Python quick start. Neither fact alone tells us whether generated browser assets are committed, whether a frontend build is mandatory, or how build output reaches Tornado.

Inspect the pinned client directory before treating backend startup as complete. Identify any package manifest, lockfile, tool-version declarations, TypeScript configuration, build configuration, and client-specific instructions. Read the scripts actually declared there rather than assuming a conventional build command.

  • Determine whether browser-ready JavaScript is already present in a clean checkout.
  • If compilation is required, establish the supported runtime, package manager, dependency-install command, and build command from that revision.
  • Identify the output directory and how Tornado templates or static-file handlers reference it.
  • Check whether development and deployed client modes expect different origins or ports.
  • Record every preparation step, then repeat it in a fresh checkout without leftover generated files.
The frontend build procedure remains unverified

The supplied excerpts do not include the client manifest, scripts, or build configuration. We cannot honestly supply an exact Node version, package-manager command, output path, or asset-copy instruction. Stop at this gate if those requirements cannot be established. The backend sequence below is documented, but it is not proof of a complete current frontend installation.

Create and verify the ddz database

The available schema.sql is explicit: it creates ddz with character set utf8mb4 and collation utf8mb4_general_ci, then creates ddz.user and ddz.record. All three creation statements use IF NOT EXISTS. The file does not provision a dedicated application account or define its grants.

TableColumnsVerified constraints and defaults
ddz.userid, openid, nameAuto-increment integer primary key; required unique openid VARCHAR(50); required name VARCHAR(50).
ddz.usersex, avatarRequired sex TINYINT(1); nullable avatar VARCHAR(256).
ddz.userdate_joined, last_modifieddate_joined DATETIME DEFAULT CURRENT_TIMESTAMP; last_modified DATETIME ON UPDATE CURRENT_TIMESTAMP.
ddz.recordid, round, robot, last_modifiedAuto-increment integer primary key; round JSON DEFAULT (JSON_OBJECT()); required robot TINYINT; modification timestamp updated on row changes.

These definitions establish storage structure, not game semantics or authentication guarantees. For example, a unique openid column does not by itself establish that incoming identities are securely authenticated. The schema also contains no seed inserts, so there is no documented seed-row count to require.

From the project root, the README's initialization command is:

mysql --user=root -p < schema.sql

The password prompt avoids putting an administrative password in the command. Use a reviewed administrative account appropriate to your environment if root access is managed differently. Import into a disposable test database environment first, not an existing installation containing valuable game data.

Verify MySQL compatibility rather than trusting the old minimum

The schema's JSON DEFAULT (JSON_OBJECT()) uses an expression default. MySQL 5.7 does not support this JSON expression default; support for expression defaults arrived in MySQL 8.0.13. The README's โ€œMySQL 5.7+โ€ statement therefore does not establish that this schema imports unchanged on 5.7. Record and test the actual server version, and do not silently modify the schema to conceal a compatibility failure.

Verify objects and column definitions

Read all import errors and check the command's exit status immediately. Then run the following read-only queries through an administrative inspection session:

SELECT VERSION();
SHOW DATABASES LIKE 'ddz';
SHOW TABLES FROM ddz;
SHOW COLUMNS FROM ddz.`user`;
SHOW COLUMNS FROM ddz.`record`;
SHOW CREATE TABLE ddz.`user`;
SHOW CREATE TABLE ddz.`record`;

For a clean import, expect the two documented tables and the definitions summarized above. IF NOT EXISTS does not upgrade an existing table with missing or outdated columns. A successful repeated import is therefore not sufficient evidence that an older database matches the pinned source.

Provision and test a dedicated account

Administrative schema creation and routine application access are different responsibilities. Have a MySQL administrator inspect the pinned application queries and provision a dedicated account restricted to the necessary operations on ddz. The evidence does not establish an exact least-privilege grant set or account-host pattern, so a universal CREATE USER/GRANT script would be speculative.

After provisioning, replace APP_DB_USER with that actual username and test the same network path the backend will use:

mysql --user=APP_DB_USER -p --host=127.0.0.1 --port=3306 ddz
SELECT CURRENT_USER(), DATABASE();
SHOW TABLES;
SELECT COUNT(*) FROM `user`;
SELECT COUNT(*) FROM `record`;

These checks establish connection, selected database, and read access. They do not establish permission to perform all application writes. Test representative database-backed game actions and inspect failures before expanding grants. Do not solve a permission problem by switching the application back to root.

Follow the documented backend startup sequence

The sequence below preserves the README's backend order while using the named repository, immutable revision, and a dedicated database account. Resolve frontend preparation separately before declaring the application usable. Review requirements.txt before installation; a pinned source file is not necessarily a fully locked dependency environment.

1

Obtain the pinned checkout

Use the clone and detached-checkout commands above. Confirm revision 7092ea77cb1273c3726ecd8b9be03d9f489eb8ed and remain in the project root.

2

Import and inspect the schema

Use the documented import, inspect errors, and verify both tables. Provision the dedicated account before configuring application access.

mysql --user=root -p < schema.sql
3

Install Python dependencies

The README uses pip3. Invoking pip through the selected interpreter makes the installation environment explicit.

python3 -m pip install -r requirements.txt
4

Enter the server directory

Launch from the directory documented for app.py.

cd server
5

Set the database connection

Replace both credential placeholders locally with the account actually provisioned. This is a URI template, not a ready-to-use credential.

export DATABASE_URI='mysql+aiomysql://APP_DB_USER:APP_DB_PASSWORD@127.0.0.1:3306/ddz'
6

Start the foreground server

Keep the terminal available for startup output and tracebacks while verifying the browser application.

python3 app.py

The export uses POSIX-shell syntax. Encode password characters with special URI meaning according to the driver's connection-URL rules. Do not publish the completed URI, commit it, or include it in screenshots and support logs. Shell history may also retain commands containing secrets; use your environment's approved secret-handling method for ongoing operation.

An environment variable normally passes to processes started from that shell. A new login, service manager, or reboot will not automatically reproduce it. The repository does not prescribe a production secret store or process supervisor, and the foreground command is not a promise of automatic restart.

Do not expose MySQL

Remote browsers need the game HTTP service, not port 3306. Keep MySQL accessible only to the application host or a controlled private network. Do not create a public database tunnel for this workflow, and protect database credentials and Localtonet device tokens.

Verify the entire local browser workflow

An illustration of a running Doudizhu server terminal beside a game page loaded from localhost.
Use the browser and backend logs together. This illustration does not replace a clean-environment test of the pinned revision.

Open http://127.0.0.1:8080 on the application host. A page response establishes only that a listener returned content. It does not prove that generated client assets exist, database writes succeed, or participants can exchange game state.

  • Confirm that the expected page loads and the Python process remains running.
  • Open browser developer tools. Check scripts, styles, images, and other assets for missing files, incorrect content types, or JavaScript errors.
  • Confirm that assets originate from the intended pinned build, not a stale browser cache or a previous checkout.
  • Follow the interface's actual game flow, including session entry and representative actions.
  • Identify requests associated with database activity and verify their effects with appropriate read-only queries. Do not invent expected row counts or assume every action creates a record.
  • If multiple participants are supported, use independent browser contexts and observe state changes in each.
  • Refresh or reconnect a browser and record actual recovery behavior.

The supplied excerpts do not establish a health-check endpoint, an automated game test, the exact session workflow, or whether browser updates use WebSocket, polling, or another mechanism. Identify the mechanism from the pinned code and browser network panel rather than asserting one.

Test again from the device that will run our client. If it is the application host, loopback is appropriate. If it is another device, 127.0.0.1 refers to that other device, not the game server. Verify an approved private address reachable from the client, including the backend's binding and local network access policy. Running our client on the game host avoids this extra LAN boundary.

Do not proceed when local verification fails. A tunnel cannot repair a missing client build, failed schema import, absent Python module, bad credential, or stopped backend.

Forward the verified HTTP service with Localtonet

Remote HTTP traffic passing through Localtonet to the local Tornado service while MySQL remains private.
Only the browser-facing service belongs in this tunnel. MySQL remains outside the public request path.

Our client establishes an outbound connection to a Localtonet relay. This provides public access without inbound router port forwarding, a public IP address, VPN setup, or inbound firewall changes for the tunnel. Use the service only where your network policy permits it; tunneling is not permission to bypass organizational controls.

HTTP tunnels support Random Sub Domain, Custom Sub Domain, or Custom Domain process types, subject to current availability and plan options. Each serves the local content at a public HTTPS address. Select available relays from the current dashboard rather than hardcoding a region or server code. For custom domains, consult the current DNS requirements before configuring records.

The following uses our standard tunnel lifecycle. Keep the HTTP tunnel documentation available for current installation and dashboard details.

1

Install and run our client

Use the device that can reach the verified Doudizhu endpoint, preferably the application host for this initial test.

2

Authenticate and select the device

Use its device-specific AuthToken. Keep the token out of the repository, screenshots, and shared logs.

3

Select an available relay

Choose a current server or region offered by the dashboard for your configuration.

4

Create the HTTP configuration

Choose the available process type. On the game host, use local IP 127.0.0.1 and port 8080. Otherwise use the game host address already verified from the client device.

5

Start and use the tunnel

Press Start, confirm the client is connected and the tunnel is running, and copy the assigned public URL for remote testing. Creating a configuration alone does not start it.

6

Stop or delete it when finished

After the verification session, stop forwarding traffic. Stop preserves the configuration; delete removes it when no longer needed.

A public URL is not authentication

Receiving or guessing an address does not establish authorization. The supplied evidence does not verify application login, room passwords, player approval, or other access controls. Treat the endpoint as publicly reachable unless effective controls have been separately reviewed and configured. HTTPS does not decide who may play.

Test remote assets, connections, and gameplay

Use a browser on a separate network when practical. Keep backend logs and our client status available, then repeat the workflow that succeeded locally:

  1. Load the assigned public HTTPS URL and confirm the expected page.
  2. Check that scripts, images, styles, and subsequent page requests succeed.
  3. Inspect browser errors for mixed content, origin checks, or hardcoded private addresses.
  4. Exercise the actual session and gameplay workflow with independent participants where supported.
  5. Verify representative database-backed actions using the dedicated account.
  6. Observe any persistent or bidirectional connections throughout the session, not just during connection establishment.
  7. Disconnect and reconnect a test browser, then compare recovery with local behavior.
  8. Review backend and browser errors before distributing the link more broadly.

A remote browser interprets 127.0.0.1 as itself. If a client script constructs a connection to localhost or a private host, the initial public page can work while gameplay fails. Compare local and public network traces, including request origins, paths, responses, and connection lifetimes.

If client and backend preparation reveals additional service ports, do not assume forwarding 8080 automatically publishes them. Determine the supported deployment arrangement and retest it. This article does not certify remote multiplayer for the observed revision without those results.

Record results, restart, and shut down safely

Availability depends on the Python process, required MySQL access, the connected Localtonet client, and a running tunnel. A configuration visible in the dashboard is not proof that all four conditions hold.

Clean-environment acceptance record

Before publishing this as a completed installation tutorial, the editor must retain a reproducible test record covering:

  • The full source SHA, operating system, Python version, and connected MySQL server version.
  • Dependency installation from an empty environment, including client tool versions and build commands if required.
  • Schema import into a clean database, with both table definitions checked.
  • Dedicated account behavior for real reads and writes, not just successful login.
  • Backend startup and static assets from a clean checkout.
  • Browser connection types and the exact local game flow tested.
  • The same flow through Localtonet, with participant count and observed limitations.
  • Restart, browser reconnection, tunnel stop, application stop, and public-access shutdown results.

These are acceptance criteria, not reported successes. The final published instructions should incorporate the verified client procedure and resolved compatibility requirements rather than leaving readers to rediscover them.

Restart behavior

Stop public forwarding during maintenance. Restart the backend from the pinned checkout's server directory with the same Python environment and a valid credential source. A new shell may lack DATABASE_URI. Verify locally before restarting public access.

After a host or client reboot, check the Localtonet device connection and tunnel state explicitly. Do not promise automatic startup without a separately configured and tested supervisor. Re-test database-backed behavior and browser reconnection; persistence of stored records does not necessarily imply persistence of an active game session.

Security and shutdown

Review debug settings, administrative handlers, input validation, dependencies, and authentication before exposure to untrusted users. A recent dependency commit is not a security audit. Use least privilege, restrict database access, and retain only redacted logs without tokens, complete connection URIs, session secrets, or personal data.

Stop the tunnel first when the session ends, then verify that the public URL no longer reaches the application. Stop the foreground Python process using your operating system's normal interruption procedure. Stop MySQL only if it is dedicated to this environment and no other service depends on it. Back up valuable data before schema changes; do not use repeated imports as a migration or recovery strategy.

Troubleshoot the failing boundary

SymptomBoundaryNext check
Schema import fails near the JSON defaultDatabase compatibilityQuery the server version and inspect the exact error. Do not rely on the README's 5.7 minimum.
Tables exist but columns differExisting schemaCompare SHOW CREATE TABLE results. IF NOT EXISTS does not migrate old tables.
Missing Python moduleInterpreter environmentInstall requirements with the interpreter used to launch app.py.
Server exits immediatelyStartupRead the traceback; check working directory, dependencies, and database configuration.
Database login failsAccount or URITest the same host and port with the dedicated account; check password encoding and account-host restrictions.
A game action receives permission errorsDatabase grantsIdentify the failed operation and adjust only justified privileges.
Blank game or missing scriptsFrontend preparationInspect client build requirements, generated outputs, template references, and browser errors.
Port 8080 is already in useListener conflictIdentify the owner with operating-system tools. Do not terminate an unknown service or invent an unsupported port setting.
Local works, public failsTunnel target or lifecycleConfirm connected device, running tunnel, and target reachability from that device.
Public page works, gameplay failsSecondary connectionsInspect private URLs, mixed content, persistent connections, and backend errors.
Failure after rebootRestart environmentRestore the backend environment, verify MySQL, connect our client, and start the tunnel.

Some database failures appear only when an action first accesses persistence. Reproduce the problem locally before changing the tunnel. Likewise, test from the Localtonet client device before blaming public routing: a successful browser on the game host says nothing about a different device's loopback address.

Frequently asked questions

Which repository and revision does this guide identify?

It identifies svzdev/doudizhu at observed commit 7092ea77cb1273c3726ecd8b9be03d9f489eb8ed. This is an immutable review target, not a tested-release claim. The README's different clone URL is not used.

Is the current installation procedure fully verified?

No. The supplied evidence establishes the revision and documented backend sequence, but not a successful clean installation. Client preparation, database compatibility, gameplay, forwarding, restart, and shutdown still require recorded execution before publication as a tested tutorial.

What does schema.sql create?

It creates ddz with utf8mb4 and utf8mb4_general_ci, plus user and record tables. The user table stores identity/profile fields and timestamps. The record table defines an ID, JSON round data, a robot field, and a modification timestamp. It does not create the application account or seed rows.

Does the Phaser client require a build?

That must be established from the pinned client files. History reports a TypeScript migration, while the root README retains the older Python-only quick start. The supplied excerpts do not establish a build command, tool version, or whether compiled assets are included.

Should I run the game with MySQL root credentials?

No. Use administrative privileges for reviewed schema setup, then provision and test a dedicated application account. Exact least-privilege grants must follow the pinned application's actual database operations.

Which endpoint should Localtonet forward?

Forward the verified HTTP service, documented at 127.0.0.1:8080 when our client runs on the game host. Use a tested reachable private address if the client runs elsewhere. Do not expose MySQL for browser gameplay.

Does sharing the public URL authorize players?

No. Reachability is not authentication. Review and test the game's access controls separately, and do not claim authenticated remote multiplayer without evidence from the pinned application.

Does creating a tunnel start it automatically?

No. Start the tunnel explicitly. Public access requires the selected client to remain connected, the tunnel to be running, and the game service to remain reachable at its configured target.

Connect a verified game endpoint with Localtonet

After resolving the pinned client's preparation and completing local validation, use our HTTP tunnel for a controlled remote test. Keep MySQL private, protect your device token, verify the complete game flow, and stop forwarding when the session ends.

Get Started Free โ†’

Corrections & updates

Substantive changes approved by the Localtonet editorial team are listed transparently below.

Remove the outer article tags while preserving the existing Localtonet component hierarchy. Update the title and introduction to identify svzdev/doudizhu explicitly. Replace the false schema-evidence caveats with an accurate explanation that schema.sql creates the ddz database and the user and record tables, including a concise description of the verified columns and suitable verification queries. State that the evidence exposes the current master SHA 7092ea77cb1273c3726ecd8b9be03d9f489eb8ed, but does not establish that this revision

Add the mandatory hero-following guide navigation card and matching IDs on all linked h2 sections. Move the hero to the beginning, remove the outer article wrapper from Model.Body, and retain only compliant lt-* structures. Resolve the svzdev versus mailgyc repository discrepancy before publication, link and pin the tested checkout, and retest the schema import, dependency installation, environment configuration, startup command, and local endpoint. Complete or explicitly qualify MySQL account provisioning, grant, schema-verification

Localtonet is a secure multi-protocol tunneling and proxy platform designed to expose localhost, devices, private services, and AI agents to the public internet supporting HTTP/HTTPS tunnels, TCP/UDP forwarding, mobile proxy infrastructure, file server publishing, latency-optimized game connectivity, and developer-ready AI agent endpoint exposure from a single unified control plane.

support