Docker Deployment
This guide shows how to run Epusdt with Docker Compose.
What the official Docker setup actually does
The source repository includes an official docker-compose.yaml example that:
- runs
gmwallet/epusdt:alpine - mounts a local
envfile to/app/.env - publishes container port
8000 - also includes a local
build:section because the source repository itself contains a rootDockerfile
The application reads configuration from .env by default. In Docker, the documented pattern is to keep a host file named env and mount it as /app/.env inside the container.
For a fresh deployment in an empty directory, the safest approach is to use the published image directly and omit the build: block unless you also copied or checked out the source repository with its Dockerfile.
Prerequisites
- Docker and Docker Compose installed
- a public domain or server address for Epusdt
- a TronGrid API key
- an
api_auth_token - optional Telegram bot settings
Step 1: Create the working directory
mkdir epusdt && cd epusdtStep 2: Create the configuration file
Save the following as env in the working directory:
cat <<EOF > env
app_name=epusdt
app_uri=https://pay.example.com
log_level=info
http_access_log=false
sql_debug=false
http_listen=:8000
static_path=/static
runtime_root_path=/runtime
log_save_path=/logs
log_max_size=32
log_max_age=7
max_backups=3
# supported values: postgres,mysql,sqlite
db_type=sqlite
# sqlite primary database config
sqlite_database_filename=
sqlite_table_prefix=
# postgres config
postgres_host=127.0.0.1
postgres_port=3306
postgres_user=mysql_user
postgres_passwd=mysql_password
postgres_database=database_name
postgres_table_prefix=
postgres_max_idle_conns=10
postgres_max_open_conns=100
postgres_max_life_time=6
# mysql config
mysql_host=127.0.0.1
mysql_port=3306
mysql_user=mysql_user
mysql_passwd=mysql_password
mysql_database=database_name
mysql_table_prefix=
mysql_max_idle_conns=10
mysql_max_open_conns=100
mysql_max_life_time=6
# sqlite runtime store config
runtime_sqlite_filename=epusdt-runtime.db
# background scheduler config
queue_concurrency=10
queue_poll_interval_ms=1000
callback_retry_base_seconds=5
tg_bot_token=
tg_proxy=
tg_manage=
api_auth_token=
order_expiration_time=10
order_notice_max_retry=0
forced_usdt_rate=
api_rate_url=
tron_grid_api_key=
EOFMinimum settings to review
At minimum, check and set these values:
| Key | Why it matters |
|---|---|
app_uri | Public URL used when Epusdt generates checkout links |
api_auth_token | Required for API request signing/authentication |
tron_grid_api_key | Recommended for stable TRON/TRC20 queries |
db_type | Defaults to sqlite; change only if you really use MySQL/PostgreSQL |
tg_bot_token / tg_manage | Optional, but useful for bot-based management |
Step 3: Create docker-compose.yaml
cat <<EOF > docker-compose.yaml
services:
epusdt:
image: gmwallet/epusdt:alpine
restart: always
volumes:
- ./env:/app/.env
ports:
- "8000:8000"
EOFIf you are running from a checked-out source tree that already includes the official root Dockerfile, you may optionally add the original build block back:
build:
context: .
dockerfile: DockerfileStep 4: Start the service
docker compose up -dCheck status:
docker compose psCheck logs:
docker compose logs -f epusdtIf Compose reports that Dockerfile cannot be found, that usually means you copied the source repo example too literally into an empty directory. Remove the build: block, or place the official Dockerfile in the same directory.
Port exposure and reverse proxy
By default, the application listens on :8000 inside the container, and the compose example publishes it as 8000:8000.
That means:
- the app is reachable directly on the host's port
8000 - you can also place Nginx, Caddy, or another reverse proxy in front of it
For production, it is usually better to publish Epusdt through a reverse proxy with HTTPS, and avoid leaving raw port 8000 exposed to the public internet unless that is an intentional choice. If you do publish 8000 directly, make sure your firewall and security group rules match that decision.
Nginx example
server {
listen 443 ssl http2;
server_name pay.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Caddy example
pay.example.com {
reverse_proxy 127.0.0.1:8000
}Subpath example, only when you really need it
The application itself is not subpath-aware. If you still want to publish it under /epusdt/, your proxy must strip that prefix before forwarding.
Example Nginx location:
location /epusdt/ {
rewrite ^/epusdt/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}Even with a rewrite like this, app_uri still needs to match the public URL users will actually open, for example https://example.com/epusdt, and you should verify both /payments/... and /pay/... paths end to end.
API path vs checkout path
Keep these paths distinct:
- create order API:
/payments/epusdt/v1/order/create-transaction - hosted checkout page:
/pay/checkout-counter/:trade_id - checkout polling endpoint:
/pay/check-status/:trade_id
app_uri is used to generate checkout URLs such as:
https://pay.example.com/pay/checkout-counter/{trade_id}It is not an internal router prefix.
Subpath caveat
The current source code registers routes at root-relative paths like /payments/... and /pay/....
If you want to expose Epusdt under a subpath such as https://example.com/epusdt/, Docker alone does not change that. You would need reverse-proxy rewrite rules that remove the prefix before the request reaches Epusdt, plus careful testing for both API and checkout routes.
For the least surprising setup, deploy Epusdt on its own domain or subdomain, for example:
https://pay.example.com
Verify installation
A practical verification flow is:
- confirm the container is running
- open
http://SERVER_IP:8000/or your proxied domain and confirm the service responds - create a test order against
/payments/epusdt/v1/order/create-transaction - confirm the returned
payment_urlpoints to/pay/checkout-counter/{trade_id}on your public domain
Upgrading
docker compose pull
docker compose up -dTroubleshooting
The container starts but uses the wrong config
Make sure the file is mounted exactly as:
volumes:
- ./env:/app/.envThe app looks for .env inside the container unless you explicitly override the config path.
Port 8000 is already in use
Change the host-side mapping, for example:
ports:
- "9000:8000"The container still listens on 8000; only the host port changes.
MySQL or PostgreSQL cannot be reached
Inside Docker, 127.0.0.1 points to the container itself, not your host database.
If your database runs in another container, use the service name. If it runs on the host, use a host-accessible address instead of assuming localhost will work.
Static assets or runtime files behave unexpectedly after changing paths
static_path controls the URL path for static files, while runtime_root_path controls filesystem storage. They are not deployment base-path settings. Avoid changing them just to try to mount the app under a URL subpath.
