Commit inicial: app de exportação da SD + deploy Docker/Coolify
Aplicação Flask (enviar SD.md -> corrigir pendências -> exportar JSON), scripts de contrato (scripts/) e infraestrutura de deploy (Dockerfile, docker-compose.yml) para VPS via Coolify. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+58
@@ -0,0 +1,58 @@
|
||||
# Imagem da aplicação de Exportação da SD — deploy em VPS via painel Coolify.
|
||||
#
|
||||
# O que NÃO entra na imagem, e por que: clientes/. O app lê
|
||||
# clientes/ses-mg/contrato/itens.yaml em tempo de execução (regras_sd.carregar_canonico)
|
||||
# e grava SD.md de volta em clientes/ses-mg/projetos/ (escrita_sd.gravar). Esses
|
||||
# são dados do cliente, versionados fora do git — entram por VOLUME montado em
|
||||
# /app/clientes. Sem esse volume, a imagem sobe e a tela inicial responde, mas o
|
||||
# primeiro upload falha com "Arquivo canônico ausente".
|
||||
#
|
||||
# Build local: docker build -t exporta-sd .
|
||||
# Run local: docker run --rm -p 5000:5000 -v "$PWD/clientes:/app/clientes" exporta-sd
|
||||
|
||||
FROM python:3.13-slim
|
||||
|
||||
# PYTHONUNBUFFERED: log do gunicorn sai na hora no painel do Coolify.
|
||||
# LANG/LC_ALL em UTF-8: identificadores de SD trazem "·" (P2·SD8) e o
|
||||
# escrita_sd monta nome de arquivo a partir deles.
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
PORT=5000
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Dependências em camada própria: mudar código não reinstala pacote.
|
||||
# requirements.txt é o mesmo do venv Windows; o gunicorn fica separado porque
|
||||
# é só do deploy (no Windows ele não instala).
|
||||
COPY requirements.txt requirements-deploy.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt -r requirements-deploy.txt
|
||||
|
||||
# Só o código. O resto (clientes/, backlog, .md da raiz) está no .dockerignore.
|
||||
COPY scripts/ ./scripts/
|
||||
COPY app/ ./app/
|
||||
|
||||
# Usuário sem privilégio. Precisa poder escrever em /app/clientes (o "Salvar
|
||||
# SD.md") e em /app/_derivados — o dono do volume no host tem de bater com
|
||||
# este UID, ou a gravação recusa com permissão negada.
|
||||
RUN mkdir -p /app/clientes /app/_derivados \
|
||||
&& useradd --create-home --uid 10001 sd \
|
||||
&& chown -R sd:sd /app
|
||||
USER sd
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
# GET / responde 200 sem sessão (renderiza a tela de upload).
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD python -c "import urllib.request,os,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:'+os.environ.get('PORT','5000')+'/',timeout=4).status==200 else 1)"
|
||||
|
||||
# -w 1 NÃO é ajuste de desempenho, é requisito de correção: o estado da sessão
|
||||
# é a global ATUAL no processo do servidor.py (uma SD por vez, em memória).
|
||||
# Com dois workers, dois requests do MESMO usuário caem em processos
|
||||
# diferentes e a sessão desaparece de forma aleatória. Pelo mesmo motivo, o
|
||||
# número de réplicas no Coolify tem de ficar em 1.
|
||||
#
|
||||
# --chdir app + servidor:app: o servidor.py insere ../scripts no sys.path a
|
||||
# partir de __file__, então funciona igual sob gunicorn e sob `python app/servidor.py`.
|
||||
CMD ["sh", "-c", "exec gunicorn --chdir app servidor:app -w 1 -b 0.0.0.0:${PORT:-5000} --timeout 120 --access-logfile - --error-logfile -"]
|
||||
Reference in New Issue
Block a user