From 682f9f659e54e20a3cd53efb8bbae1c1f9745e72 Mon Sep 17 00:00:00 2001 From: Thiago Zilli Sarmento Date: Mon, 28 Sep 2026 09:28:26 -0300 Subject: [PATCH] feat: support external Docker database seed --- .dockerignore | 5 +- .gitignore | 3 +- Dockerfile | 43 ++++++++++++---- Makefile | 25 +++++++-- README.md | 99 +++++++++++++++++++++++++++++++----- docker-compose.portainer.yml | 35 +++++++++++++ docker-compose.yml | 4 +- docker-entrypoint.sh | 34 +++++++++++++ 8 files changed, 216 insertions(+), 32 deletions(-) create mode 100644 docker-compose.portainer.yml create mode 100644 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore index 89d631a..ae0ccf0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,4 +9,7 @@ data/ .vscode/ .idea/ brasilcep-webservice -wserver \ No newline at end of file +wserver +.git/ +data.tar.gz +data-dne.tar.gz diff --git a/.gitignore b/.gitignore index f285c14..909ba3f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ tmp_dne/ .vscode/ .idea/ brasilcep-webservice -wserver \ No newline at end of file +wserver +data-dne.tar.gz \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7e5416c..ecd7b49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,45 @@ -FROM alpine:latest AS base -RUN apk update && apk upgrade +# syntax=docker/dockerfile:1 -FROM base AS builder +FROM golang:1.24-alpine AS builder -RUN apk add go make git +WORKDIR /src -WORKDIR /app +# Keep dependency downloads cached when only application sources change. +COPY go.mod go.sum ./ +RUN go mod download COPY . . -RUN make build +ARG VERSION=dev +ARG COMMIT=none +ARG REPO=github.com/brasilcep/api + +RUN CGO_ENABLED=0 GOOS=linux go build \ + -trimpath \ + -ldflags="-s -w \ + -X 'main.Version=${VERSION}' \ + -X 'main.Commit=${COMMIT}' \ + -X 'main.Repo=${REPO}' \ + -X 'main.Compiler=$(go version | awk '{print $3}')'" \ + -o /out/wserver ./main.go -FROM base AS final +FROM alpine:3.22 AS final + +RUN apk add --no-cache su-exec tar && \ + addgroup -S brasilcep && \ + adduser -S -G brasilcep -h /app brasilcep WORKDIR /app -COPY --from=builder /app/data.tar.gz /app/data.tar.gz +COPY --from=builder /out/wserver ./wserver +COPY --chown=brasilcep:brasilcep --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh + +ENV DB_PATH=/app/data \ + MODE=listen -RUN tar -xzf data.tar.gz && rm -rf data.tar.gz +EXPOSE 8080 -COPY --from=builder /app/wserver /app/wserver +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ + CMD wget -q -O /dev/null http://127.0.0.1:8080/healthcheck || exit 1 -CMD ["./wserver"] +ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/Makefile b/Makefile index 3f423d2..6fb56aa 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,11 @@ VERSION := $(shell \ GO_VERSION := $(shell go version | awk '{print $$3}') REPO := github.com/brasilcep/api +IMAGE_NAME ?= brasilcep/api +IMAGE_TAG ?= dev +DOCKER_IMAGE := $(IMAGE_NAME):$(IMAGE_TAG) + +.PHONY: build test lint docker-build docker-push docker-publish run-docker benchmark-docker build: @echo "Building version $(VERSION) (commit: $(COMMIT))" @@ -28,16 +33,26 @@ lint: golangci-lint run --timeout 5m docker-build: - tar -czf data.tar.gz data/ - docker build -t brasilcep/api:dev . + @echo "Building Docker image $(DOCKER_IMAGE)" + docker build \ + --build-arg VERSION="$(VERSION)" \ + --build-arg COMMIT="$(COMMIT)" \ + --build-arg REPO="$(REPO)" \ + -t "$(DOCKER_IMAGE)" . + +docker-push: + docker push "$(DOCKER_IMAGE)" + +docker-publish: docker-build + docker push "$(DOCKER_IMAGE)" -run-docker: +run-docker: docker run --cpus="0.5" --memory="512m" \ -p 8080:8080 \ - brasilcep/api:dev + "$(DOCKER_IMAGE)" benchmark-docker: docker compose up brasilcep-api -d @echo "Waiting for the API to start and load data..." sleep 120 - docker compose up \ No newline at end of file + docker compose up diff --git a/README.md b/README.md index 4327e87..c107182 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ API REST para consulta de CEPs brasileiros, baseada no Diretório Nacional de En Projeto open source, rápido, eficiente e fácil de usar. ## Sumário + - [Sumário](#sumário) - [Como rodar](#como-rodar) - - [Docker (base de CEPs pré fabricada inclusa)](#docker-base-de-ceps-pré-fabricada-inclusa) + - [Docker](#docker) - [Desenvolvimento / sem docker](#desenvolvimento--sem-docker) - [Configurações via ENV](#configurações-via-env) - [Importação da base DNE](#importação-da-base-dne) @@ -32,36 +33,73 @@ Projeto open source, rápido, eficiente e fácil de usar. ## Como rodar -### Docker (base de CEPs pré fabricada inclusa) +### Docker + +```bash +docker run -p 8080:8080 \ + -e DATA_DNE_EXTERNAL="https://exemplo.com/data-dne.tar.gz" \ + brasilcep/api:latest +``` + +Quando `DB_PATH` ainda não contém uma base, `DATA_DNE_EXTERNAL` pode apontar para +um arquivo `data-dne.tar.gz`. O container baixa e extrai a base antes de iniciar. +Sem essa variável, nenhum download ou carregamento de base é realizado. + +Para construir e publicar a imagem em seu próprio Docker Hub: + +```bash +docker login +make docker-publish \ + IMAGE_NAME=seu_usuario/brasilcep \ + IMAGE_TAG=1.0.0 \ + VERSION=1.0.0 +``` + +`IMAGE_NAME` define o repositório no Docker Hub, `IMAGE_TAG` define a tag da +imagem e `VERSION` define a versão exibida pela API. Os valores padrão continuam +sendo `brasilcep/api`, `dev` e a versão derivada do Git, respectivamente. + +Também é possível executar as etapas separadamente: + ```bash -docker run -p 8080:8080 brasilcep/api:latest -``` +make docker-build IMAGE_NAME=seu_usuario/brasilcep IMAGE_TAG=1.0.0 VERSION=1.0.0 +make docker-push IMAGE_NAME=seu_usuario/brasilcep IMAGE_TAG=1.0.0 +``` ### Desenvolvimento / sem docker 1. Clone o repositório: + ```sh git clone https://github.com/brasilcep/api.git cd brasilcep-webservice ``` + 2. Instale o Go (>=1.18). 3. Instale as dependências: + ```sh go mod tidy ``` + 4. Execute o serviço: + ```sh go run main.go ``` + Ou utilize o Makefile: + ```sh make run ``` Para compilar o binário localmente: + ```sh make build ``` + O binário será gerado em `./wservice`. ## Configurações via ENV @@ -87,10 +125,12 @@ Todas as configurações podem ser definidas via variáveis de ambiente: - **DB_PATH**: Caminho para os arquivos do banco BadgerDB. Padrão: `./data` - **DB_RAW_PATH**: Caminho para os arquivos originais do DNE. Padrão: `./dne` +- **DATA_DNE_EXTERNAL**: URL opcional de um arquivo `data-dne.tar.gz`. Quando `DB_PATH` ainda não contém uma base, o container baixa, extrai e remove esse arquivo antes de iniciar a API. A URL não é utilizada quando a base já existe. - **LOG_FORMAT**: Formato do log ("json" ou "text"). Padrão: `json` - **LOG_LEVEL**: Nível de log ("debug", "info", "warn", "error"). Padrão: `info` Exemplo de uso: + ```sh export API_PORT=8081 export LOG_LEVEL=debug @@ -99,16 +139,20 @@ export LOG_LEVEL=debug ## Importação da base DNE Para importar a base oficial dos Correios (DNE): + 1. Baixe os arquivos TXT do DNE e coloque-os na pasta definida em `DB_RAW_PATH` (por padrão, `./dne`). 2. Execute o importador: + ```go import "github.com/brasilcep/api/zipcodes" zipImporter := zipcodes.NewZipCodeImporter(logger) zipImporter.PopulateZipcodes("./dne") ``` + Ou execute o modo seed Arquivos necessários: + - LOG_LOCALIDADE.TXT - LOG_BAIRRO.TXT - LOG_LOGRADOURO_XX.TXT (para cada UF) @@ -124,7 +168,9 @@ O modo `seed` serve para importar a base oficial dos Correios (DNE) para o banco export MODE=seed go run main.go ``` + Ou via Makefile: + ```sh MODE=seed make run ``` @@ -138,7 +184,8 @@ O importador lê todos os arquivos TXT necessários e popula o banco local. Apó Coloque todos os arquivos TXT do DNE na pasta definida por `DB_RAW_PATH` (por padrão, `./dne`). Exemplo: -``` + +```text ./ ├── main.go ├── dne/ @@ -184,12 +231,17 @@ Após importar, rode o projeto normalmente em modo `listen` para servir a API. ## Endpoints da API ### `GET /cep/:cep` + Consulta um CEP. + - **Exemplo:** + ```sh curl http://localhost:8080/cep/01310930 ``` + - **Resposta:** + ```json { "cep": "01310930", @@ -203,17 +255,23 @@ Consulta um CEP. "nome_origem": "LOG_LOGRADOURO_SP.TXT" } ``` + - **Erros:** - - 404: CEP não encontrado - - 400: CEP não fornecido + - 404: CEP não encontrado + - 400: CEP não fornecido ### `GET /healthcheck` + Verifica o status do serviço. + - **Exemplo:** + ```sh curl http://localhost:8080/healthcheck ``` + - **Resposta:** + ```json { "status": "ok", @@ -224,12 +282,17 @@ Verifica o status do serviço. ``` ### `GET /debug/list?prefix=XXXXX` + Lista até 100 CEPs, opcionalmente filtrando por prefixo. + - **Exemplo:** + ```sh curl "http://localhost:8080/debug/list?prefix=01310" ``` + - **Resposta:** + ```json { "total": 100, @@ -238,12 +301,17 @@ Lista até 100 CEPs, opcionalmente filtrando por prefixo. ``` ### `GET /debug/count` + Conta o total de CEPs cadastrados. + - **Exemplo:** + ```sh curl http://localhost:8080/debug/count ``` + - **Resposta:** + ```json { "total_ceps": 123456 @@ -251,12 +319,17 @@ Conta o total de CEPs cadastrados. ``` ### `GET /debug/stats` + Estatísticas dos CEPs por UF. + - **Exemplo:** + ```sh curl http://localhost:8080/debug/stats ``` + - **Resposta:** + ```json { "total_ceps": 123456, @@ -275,10 +348,11 @@ Estatísticas dos CEPs por UF. ## Benchmarks -You can run `make benchmark-docker`. +You can run `make benchmark-docker`. We've tested using c0.5m0.5 container with ssd disk and results goes to: -``` + +```text checks.........................: 100.00% ✓ 29614 ✗ 0 data_received..................: 12 MB 403 kB/s data_sent......................: 2.9 MB 96 kB/s @@ -299,12 +373,13 @@ vus_max........................: 100 min=100 max=100 ``` ## Licença -MIT +MIT Este projeto está licenciado sob a Licença MIT - consulte o arquivo [LICENSE](LICENSE) para mais detalhes. **Resumo da Licença:** + - Uso comercial permitido - Modificação permitida - Distribuição permitida @@ -312,6 +387,6 @@ Este projeto está licenciado sob a Licença MIT - consulte o arquivo [LICENSE]( - Sem garantia - Exige atribuição do autor - ## Contribua -Pull requests são bem-vindos! Abra issues para sugestões ou problemas. \ No newline at end of file + +Pull requests são bem-vindos! Abra issues para sugestões ou problemas. diff --git a/docker-compose.portainer.yml b/docker-compose.portainer.yml new file mode 100644 index 0000000..5582fe4 --- /dev/null +++ b/docker-compose.portainer.yml @@ -0,0 +1,35 @@ +services: + go-brasilcep: + image: thiagozs/go-brasilcep:latest + environment: + DB_PATH: /app/data + DATA_DNE_EXTERNAL: ${DATA_DNE_EXTERNAL} + volumes: + - go-brasilcep-data:/app/data + ports: + - target: 8080 + published: 8080 + protocol: tcp + mode: ingress + networks: + - go-brasilcep-network + - proxy_net + deploy: + replicas: 1 + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + window: 120s + update_config: + order: stop-first + failure_action: rollback + +networks: + go-brasilcep-network: + driver: overlay + proxy_net: + external: true + +volumes: + go-brasilcep-data: diff --git a/docker-compose.yml b/docker-compose.yml index 3193565..91a0846 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: cpus: '0.5' memory: 512M healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:8080/healthcheck"] interval: 30s timeout: 10s retries: 3 @@ -24,4 +24,4 @@ services: - brasilcep-api volumes: - ./benchmark:/benchmark - entrypoint: ["sh", "-c", "sleep 10 && k6 run /benchmark/get.benchmark.js"] \ No newline at end of file + entrypoint: ["sh", "-c", "sleep 10 && k6 run /benchmark/get.benchmark.js"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..f93d2de --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -eu + +# Named volumes are initially mounted as root. Prepare the database directory +# before dropping privileges for both extraction and application execution. +mkdir -p "${DB_PATH}" +chown -R brasilcep:brasilcep "${DB_PATH}" + +# Extract on the container's writable layer so Badger can update its files. +# --sparse recreates the large value-log holes without allocating them on disk. +if [ ! -f "${DB_PATH}/MANIFEST" ]; then + if [ -n "${DATA_DNE_EXTERNAL:-}" ]; then + archive="$(mktemp /tmp/data-dne.tar.gz.XXXXXX)" + trap 'rm -f "${archive}"' EXIT HUP INT TERM + + echo "Database not found at ${DB_PATH}; downloading external database" + wget -T 60 -O "${archive}" "${DATA_DNE_EXTERNAL}" + echo "Database archive downloaded ($(wc -c < "${archive}") bytes); extracting" + chown brasilcep:brasilcep "${archive}" + + su-exec brasilcep tar --sparse -xzf "${archive}" --strip-components=1 -C "${DB_PATH}" + + if [ ! -f "${DB_PATH}/MANIFEST" ]; then + echo "External database archive does not contain data/MANIFEST" >&2 + exit 1 + fi + + rm -f "${archive}" + trap - EXIT HUP INT TERM + echo "External database extracted successfully" + fi +fi + +exec su-exec brasilcep /app/wserver "$@"