Python em produção

Deploy passo a passo de uma aplicação Python

Antes de começar

sudo apt update
sudo apt upgrade -y
sudo reboot

Criar user, se não existir: sudo useradd $NEW_USER

Então configurar senha: sudo passwd $NEW_USER

não perca a senha nova!!!

1Adicionar novo user ao sudoers

sudo echo "$NEW_USER ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/config-$NEW_USER-sudo"

2Configure deploy key

ssh-keygen
eval "$(ssh-agent -s)"
ssh-add .ssh/id_ed25519
cat .ssh/id_ed25519.pub

Colocar no repositório em Settings > Deploy Keys.

3Clonar repo

git clone git@github.com:user/repo.git
cd repo

4Configurar virtualenv

Se necessário:

sudo apt install python3-dev python3-venv build-essential gcc g++ pkg-config

Configurar virtualenv:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

5Config Django

python manage.py migrate
python manage.py createsuperuser

6Config Caddy

sudo apt install caddy
sudo vi /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl enable caddy
seudominio.com.br {
    log {
        output file /var/log/caddy/access.log
        format console
    }

    handle /static/* {
        root * /var/staticfiles
        file_server
    }

    handle {
        reverse_proxy localhost:8080
    }
}

7Arquivos estáticos

sudo mkdir -m 755 /var/staticfiles/
sudo chown ubuntu:ubuntu /var/staticfiles
python manage.py collectstatic

8Configuração swap

sudo fallocate -l 1G /swapfile # trocar tamanho de acordo com a sua memoria
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
swapon --show

9Config unattended-upgrades

sudo apt install unattended-upgrades

Editar /etc/apt/apt.conf.d/20auto-upgrades:

APT::Periodic::Enable "1";
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

Then enable the schedulers:

sudo systemctl enable --now apt-daily.timer apt-daily-upgrade.timer

Test without installing anything:

sudo unattended-upgrade --dry-run

Your existing /etc/apt/apt.conf.d/50unattended-upgrades already limits automatic installation to Ubuntu and ESM security repositories. Normal noble-updates packages will not be installed automatically, and automatic reboot remains disabled.

10Config UFW

sudo apt install ufw
sudo ufw enable
sudo ufw default allow outgoing
sudo ufw default deny incoming
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443

11Config SSHD

sudo vim /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
sudo sshd -t
sudo systemctl restart ssh

12Deploy automático com cron

crontab -e

* * * * * /home/ubuntu/repo/scripts/deploy.sh

Para conferir se está rodando: grep CRON /var/log/syslog

Configurar fail2ban

sudo apt install -y fail2ban

sudoedit /etc/fail2ban/jail.d/sshd.local

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 179.228.151.152
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
backend = systemd
port = 22
mode = normal
banaction = ufw

Validate and activate it:

sudo fail2ban-client -t
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban

Verify:

sudo systemctl status fail2ban
sudo fail2ban-client status sshd
sudo ufw status numbered

Config service via systemd

Referência: Gunicorn, systemd.

systemd can create a UNIX socket and launch Gunicorn on demand.

Service file

# /etc/systemd/system/gunicorn.service

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
Type=notify
NotifyAccess=main
User=someuser
Group=someuser
WorkingDirectory=/home/someuser/applicationroot
EnvironmentFile=/var/www/myproject/.env
ExecStart=/usr/bin/gunicorn applicationname.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Type=notify lets Gunicorn report readiness to systemd. If the service should run under a transient user consider adding DynamicUser=true. Tighten permissions further with ProtectSystem=strict if the app permits.

Socket activation file

# /etc/systemd/system/gunicorn.socket

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660

[Install]
WantedBy=sockets.target

Enable and start the socket so it begins listening immediately and on reboot:

systemctl enable --now gunicorn.socket

Test connectivity from the nginx user (Debian defaults to www-data):

sudo -u www-data curl --unix-socket /run/gunicorn.sock http

Use systemctl show --value -p MainPID gunicorn.service to retrieve the main process ID or systemctl kill -s HUP gunicorn.service to send signals.

Nginx proxy

user www-data;
...
http {
    server {
        listen          8000;
        server_name     127.0.0.1;
        location / {
            proxy_pass http://unix:/run/gunicorn.sock;
        }
    }
}
...

Adjust listen and server_name for production (typically port 80 and your site's domain).

systemctl enable nginx.service
systemctl start nginx

Browse to http://127.0.0.1:8000/ to verify Gunicorn + Nginx + systemd.

  • Socket vs. Port: In a non-Docker environment, it is often more efficient to bind Gunicorn to a Unix socket (e.g., unix:/run/gunicorn.sock) rather than a TCP port to reduce overhead.
  • Worker Selection: A common rule of thumb for the --workers flag is (2 x number of cores) + 1.

Banco de dados

sudo apt install mariadb-server
sudo mariadb
CREATE DATABASE cognitrilha;
USE cognitrilha;
CREATE USER 'cognitrilha'@'localhost' IDENTIFIED BY '<sua_senha_aqui>';
GRANT ALL PRIVILEGES ON cognitrilha.* TO 'cognitrilha'@'localhost';

Redis server

sudo apt install redis-server -y
sudo vim /etc/redis/redis.conf

Add lines:

supervised auto
maxmemory 256mb
maxmemory-policy allkeys-lru
sudo systemctl restart redis-server