Deploying a MetaTrader 5 Trading Bot to the Cloud
Ship an MT5 trading bot as a plain Python process on any Linux box, container, or Raspberry Pi — no terminal, no Wine, no Windows VPS. Docker + systemd included.
The traditional way to run a MetaTrader 5 bot in production is a Windows VPS with the terminal open around the clock. You RDP in to check it's still alive, you babysit the terminal through updates and reboots, and your whole strategy is pinned to one Windows box that has to keep a GUI application running forever. If that VPS hiccups, your bot goes dark and you find out later.
There's a simpler shape. When the broker connection is hosted, your bot stops being a Windows appliance and becomes what it always should have been: a normal Python process. It reads ticks, makes decisions, and places orders over HTTP and WebSocket. That process doesn't care what operating system it's on. It runs on a Linux VM, inside a container, or on a Raspberry Pi in the corner of your desk — because nothing on that machine needs a MetaTrader terminal.
If you're new to driving MT5 from Python this way, start with running MetaTrader 5 from Python on Linux for the basics. This post is about taking that bot to production.
A minimal but real bot
Here's the whole thing: connect an account, stream ticks, and act on one. It's deliberately small so the deployment concerns stand out, but every call is a real one you'd ship.
import os
from tickerall import Tickerall, TickerallServiceUnavailableError, TickerallBrokerError
client = Tickerall(api_key=os.environ["TICKERALL_API_KEY"])
# keep_alive: a connection that survives restarts and outages. Credentials live
# in this process's memory only; they are never persisted anywhere.
session = client.sessions.keep_alive(
broker="mt5",
server=os.environ["MT5_SERVER"],
account=int(os.environ["MT5_ACCOUNT"]),
password=os.environ["MT5_PASSWORD"],
)
def on_tick(e):
# Trivial illustrative rule — replace with your real strategy.
if e.symbol == "BTCUSDm" and e.ask and e.ask < 100_000:
try:
order = client.orders.place(
session.account_id,
type="market",
symbol="BTCUSDm",
side="BUY",
volume=0.10,
)
print("placed", order)
except TickerallServiceUnavailableError as err:
# Transient — the retry is safe (see the note on idempotency below).
print("temporary outage, will retry next tick:", err.transient)
except TickerallBrokerError as err:
# The broker rejected it (margin, market closed, bad volume) — don't retry blindly.
print("broker rejected order:", err)
stream = client.stream.connect()
stream.on("tick", on_tick) # on(event, callback) — not a decorator
stream.subscribe_ticks(session.account_id, ["BTCUSDm"])
Two things to notice. First, stream.on(...) takes a callback — the stream runs
on its own background thread, handling heartbeats, reconnects, and re-subscribing
after a drop for you. Second, there's no run_forever(). In a standalone
process you keep it alive yourself:
import time
try:
while True:
time.sleep(60)
except KeyboardInterrupt:
stream.close()
client.sessions.stop_keep_alive(session.account_id)
For a deeper look at the tick stream — event shapes, subscribing to many symbols, back-pressure — see streaming live MT5 tick data over WebSocket in Python.
Make it survive restarts
Production processes restart. The host reboots, you deploy a new version, the orchestrator moves the container. The question that matters is: what happens to your broker connection when your process comes back?
That's what sessions.keep_alive is for. You hand it the credentials once, and it
keeps the broker session warm. If the account goes cold — a broker-side drop, a
network blip, an outage on either end — the next call transparently re-supplies
the credentials and retries once, so your code doesn't have to hand-roll a
reconnect loop. The credentials themselves stay in your process's memory for the
lifetime of the run and are never written to disk.
When your process shuts down cleanly, tell the service to stop keeping the connection warm:
client.sessions.stop_keep_alive(session.account_id)
State-changing calls like orders.place carry an idempotency key under the hood,
so a retry after a transient failure can't accidentally place the order twice.
That's why the except TickerallServiceUnavailableError branch above can safely
retry: err.transient tells you it's worth another attempt, and the idempotency
key guarantees you won't double-fill.
Never hardcode credentials. Everything the bot needs — the API key, the broker server, the account number, the password — comes from environment variables. They never appear in your source, your image, or your git history. The hosted session holds them in memory only and persists nothing, so a leaked repo or a snapshotted disk gives up no secrets.
Package it in a container
Because the bot is a plain Python process with no terminal, no Wine layer, and no GUI, the container is boring in the best way — a slim Python base, your code, and the SDK:
FROM python:3.12-slim
WORKDIR /app
# Install deps first so this layer caches across code changes.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY bot.py .
# Run as a non-root user.
RUN useradd --create-home bot
USER bot
CMD ["python", "-u", "bot.py"]
Your requirements.txt is a single line:
tickerall
Pass the secrets in at run time — never bake them into the image:
docker build -t mt5-bot .
docker run -d --name mt5-bot --restart unless-stopped \
-e TICKERALL_API_KEY \
-e MT5_SERVER \
-e MT5_ACCOUNT \
-e MT5_PASSWORD \
mt5-bot
The -e VAR form (no =value) forwards the value from your shell or your
orchestrator's secret store, so nothing sensitive lands on the command line. With
--restart unless-stopped, Docker brings the bot back after a crash or a host
reboot, and keep_alive re-warms the broker session on the way up.
Or run it under systemd on a plain VM
Not everything needs a container. On a bare Linux VM, a systemd unit gives you the same restart resilience with journal logging for free. Drop your secrets in a root-only env file:
# /etc/mt5-bot.env (chmod 600, owned by root)
TICKERALL_API_KEY=ta_live_xxx
MT5_SERVER=Exness-MT5Trial
MT5_ACCOUNT=12345678
MT5_PASSWORD=your-broker-password
# /etc/systemd/system/mt5-bot.service
[Unit]
Description=MT5 trading bot
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=bot
EnvironmentFile=/etc/mt5-bot.env
WorkingDirectory=/opt/mt5-bot
ExecStart=/opt/mt5-bot/venv/bin/python -u bot.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now mt5-bot
journalctl -u mt5-bot -f # follow the logs
EnvironmentFile keeps the credentials out of the unit file and out of process
listings, Restart=on-failure handles crashes, and After=network-online.target
makes sure the box has networking before your bot tries its first connect. That's
the entire ops surface — no terminal to keep open, no GUI session, no RDP.
The ops win, stated plainly. A Windows-VPS-with-terminal setup means a GUI app that must stay logged in, a heavier and pricier instance, manual terminal updates, and a single-OS lock-in. Here the deployable unit is a stateless Linux process. It fits your existing container platform or systemd, scales by running more copies, and moves between hosts without carrying a terminal along.
Wrap-up
Once the broker connection is hosted, deploying an MT5 bot stops being a MetaTrader problem and becomes an ordinary devops problem — one you already know how to solve. Stream ticks, place orders, keep the session warm across restarts, feed secrets through the environment, and run it wherever your other services run: a container, a VM, or a Pi. No Wine, no Windows, no terminal babysitting.
The full API reference — sessions, streaming, orders, candles, and history — is at tickerall.com/docs.