Basic deployment
so it runs without you and survives a reboot
What it is
Deployment is moving the code to where it runs permanently: on a server, not on your laptop with a terminal left open.
This article gives the minimum that is enough for a bot or a small service. Not Kubernetes and not cloud platforms — a simple scheme you can understand in full and repair by hand.
What it looks like in code
What has to be ready before the move
| Ready | Why |
|---|---|
requirements.txt is current | so the same things get installed (article 5.6) |
| Secrets from the environment | so .env isn't dragged into the repository (5.7) |
.gitignore configured | so nothing extra travels along |
| Code in git | so you update with git pull, not by copying files |
Logs via logging | otherwise you'll never know what happened on the server (4.2) |
If even one of those is missing, deployment turns into debugging blind.
The move
git clone <address> /opt/weather-bot
cd /opt/weather-bot
python3 -m venv .venv
.venv/bin/pip install -r requirements.txtThe secrets are created on the server separately, by hand:
nano .env # write in BOT_TOKEN
chmod 600 .env # readable by the owner onlyStarting automatically
A process started in a terminal dies when you close the ssh session. You need the system to start it itself — and restart it after a crash and after a reboot. On Linux, systemd does that.
The file /etc/systemd/system/weather-bot.service:
[Unit]
Description=Weather bot
After=network.target
[Service]
WorkingDirectory=/opt/weather-bot
EnvironmentFile=/opt/weather-bot/.env
ExecStart=/opt/weather-bot/.venv/bin/python bot.py
Restart=always
RestartSec=5
User=botuser
[Install]
WantedBy=multi-user.targetLine by line:
WorkingDirectory— the folder it starts from; relative paths in the code are resolved from here;EnvironmentFile— where to take the environment variables from, that is, the secrets;ExecStart— the full path to the Python inside the virtual environment, not the system one;Restart=always+RestartSec=5— restart on a crash, with a five-second pause;User— whose name it runs under; notroot.
The commands:
sudo systemctl daemon-reload # re-read the unit files
sudo systemctl enable weather-bot # start it at boot
sudo systemctl start weather-bot # start it now
sudo systemctl status weather-bot # how is it doing
sudo journalctl -u weather-bot -f # watch the logs liveThe last command is the main one for debugging on a server. It is the
replacement for print on a production machine.
Updating
cd /opt/weather-bot
git pull
.venv/bin/pip install -r requirements.txt
sudo systemctl restart weather-bot
sudo journalctl -u weather-bot -n 50Four commands plus a look at the logs. The last line is mandatory: an update without a check is a deployment done blind.
What to tell the AI
Write a systemd unit for this project. Path /opt/weather-bot,
runs bot.py from .venv, secrets from .env, restart on crash,
not as root. Explain every line.The bot restarts every few seconds. Here is the journalctl
output for the last minute. What is happening?
<log>Write an update script: git pull, dependencies, restart, show
the last 50 lines of the log. If git pull brought nothing,
do nothing.What to look for
.env missing on the server. The service starts and immediately dies.
In the logs — that clear message from article 5.7, if it was written.
The system Python instead of .venv. ExecStart points at
/usr/bin/python3, which has none of the project's libraries.
Relative paths. The code opens data/bot.db while the service was
started from a different folder. Hence WorkingDirectory.
Running as root. A bug in the code gets permissions over the whole system. A separate user is five minutes of work.
No log rotation. journalctl limits its own size, but logs written to
your own file grow without end until the disk fills.
Updating by copying files. A month later the server has a different version from git and nobody knows which.
Cheat sheet
Before the move: requirements, secrets from the environment,
.gitignore, git, logs.Secrets are created on the server by hand,
chmod 600.systemd:
WorkingDirectory,EnvironmentFile, the full path to.venv,Restart=always, not root.Logs:
journalctl -u <service> -f.Updating:
git pull→ dependencies →restart→ look at the logs.Restart=alwayshides crashes: check the logs, not whether a process exists.