Programming with AI
ENRU
5.8

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

ReadyWhy
requirements.txt is currentso the same things get installed (article 5.6)
Secrets from the environmentso .env isn't dragged into the repository (5.7)
.gitignore configuredso nothing extra travels along
Code in gitso you update with git pull, not by copying files
Logs via loggingotherwise 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.txt

The secrets are created on the server separately, by hand:

nano .env          # write in BOT_TOKEN
chmod 600 .env     # readable by the owner only

Starting 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.target

Line 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; not root.

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 live

The 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 50

Four 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

Prompt
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.
Prompt
The bot restarts every few seconds. Here is the journalctl
output for the last minute. What is happening?

<log>
Prompt
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=always hides crashes: check the logs, not whether a process exists.