Skip to Content
Desenvolvimento de Apps IoTArmazenamento de arquivos

Armazenamento de Arquivos

Todo backend de dados de app vem com um armazenamento de objetos privado ao lado das suas tabelas. Onde o backend de dados guarda suas linhas de séries temporais, o armazenamento de arquivos guarda tudo o que não cabe em uma linha: frames de câmera, relatórios em PDF, blobs de firmware, clipes de áudio, exportações.

Os dois foram projetados para serem usados juntos. Armazenar um arquivo lhe devolve uma URL permanente, e o padrão pretendido é escrever essa URL em uma coluna de tabela no mesmo gesto — um widget de dashboard então renderiza a imagem sem nenhum trabalho adicional:

info = await ironflock.files.put("part-1.jpg", jpeg_bytes, content_type="image/jpeg") await ironflock.publish_to_table("inspections", part_id="1", photo_url=info.url)

A API completa do cliente — leitura, listagem, uso, links de compartilhamento, objetos grandes, códigos de erro — está documentada na referência do SDK. Esta página cobre o lado da plataforma: como o armazenamento é declarado, governado, compartilhado e operado.

Como Funciona

  1. Opcionalmente, declare uma seção files: em .ironflock/data-template.yml.
  2. Quando um usuário instala o seu app em um projeto, o IronFlock provisiona uma área de armazenamento privada para ele — exatamente como provisiona o banco de dados do projeto.
  3. O seu código de borda armazena e lê objetos através da API files do SDK.
  4. Desinstalar o app remove o seu armazenamento completamente: cada objeto e cada credencial, assim como o esquema de banco de dados é descartado.

Assim como o banco de dados, o armazenamento é por projeto. O mesmo app instalado em dois projetos recebe duas áreas de armazenamento totalmente separadas e, como desenvolvedor do app, você não tem acesso a nenhuma delas — os dados pertencem ao usuário que executa o seu app.

Configuração zero é uma configuração válida: um app sem uma seção files: ainda recebe um namespace chamado default, de modo que files.put(...) funciona de imediato para todo app.

Declarando o Armazenamento no Data Template

A seção files: fica ao lado de data: no data-template.yml:

files: description: Camera frames and generated inspection reports. # Orçamento de armazenamento que o app SUGERE para si mesmo, em bytes (aqui 5 GiB). # O usuário do projeto pode sobrescrevê-lo; a configuração dele é a que é imposta. quotaBytes: 5368709120 namespaces: - name: frames description: Raw camera frames, one JPEG per inspected part. contentTypes: ["image/jpeg"] maxObjectBytes: 20971520 retention: { deleteAfter: 30 days } - name: reports description: Generated PDF inspection reports. contentTypes: ["application/pdf"] private: true

Namespaces

Um namespace é um prefixo de chave que carrega política. Ele não é um bucket de armazenamento separado — todo namespace de um app vive dentro da única área de armazenamento do app, e o nome do namespace simplesmente se torna o primeiro segmento do caminho de armazenamento de cada objeto.

Essa distinção lhe diz quando declarar um:

  • Organizando arquivos? Use caminhos de chave — 2026/03/part-1.jpg — dentro do namespace default. Chaves em estilo de pastas são o caso normal.
  • Regras diferentes para um conjunto de objetos? Declare um namespace. Regras são a única coisa que um namespace adiciona.

As regras que um namespace pode carregar:

CampoSignificado
nameLetras minúsculas, dígitos e hifens, começando com uma letra. sys, system, ironflock e ironflock-* são reservados
descriptionExibida aos usuários e legível por agentes de IA
privateExclui o namespace do acesso entre apps. O padrão é false (compartilhado), exatamente como as tabelas
contentTypesTipos MIME permitidos, globs são aceitos (image/*). Padrão: qualquer um
maxObjectBytesMaior objeto individual, até 5 GiB. Padrão de 100 MiB
retention.deleteAfterObjetos mais antigos que isso são excluídos automaticamente (30 days, 2 weeks, 1 year, …)

O Orçamento de Armazenamento

quotaBytes é declarado uma vez para o app inteiro, não por namespace. Um namespace é apenas um prefixo de chave, então não há nada contra o que um orçamento por prefixo pudesse ser imposto — a cota se aplica à única área de armazenamento do app, e o próprio object store a impõe em todo caminho de escrita, incluindo uploads diretos.

Existem dois números, e a diferença é deliberada:

  • A cota sugerida — o que o seu template pede. Aplicada quando o app é instalado pela primeira vez.
  • A cota imposta — o que o usuário do projeto definiu. Uma vez que um usuário altera o orçamento nas configurações de armazenamento do app, o valor dele prevalece, e reimplantar o seu app não o redefine.

Se o seu template não disser nada, o padrão da plataforma se aplica (1 GiB para backends de desenvolvimento, 10 GiB para os de produção).

Retenção

Objetos que ultrapassam a idade deleteAfter do seu namespace são removidos automaticamente — o análogo, do lado dos arquivos, da política dropAfter das tabelas. A retenção roda dentro do object store onde o store a suporta, e como uma rotina diária da plataforma onde não a suporta (appliances on-premises), de modo que a retenção declarada se comporta da mesma forma em todos os lugares.

URLs Permanentes e Dashboards

Todo objeto armazenado tem uma URL estável no formato https://files.ironflock.com/f/<backend>/<namespace>/<key>. Três propriedades fazem dela a coisa certa para escrever em uma coluna de tabela:

  • Ela nunca expira. A URL é um endereço puro; permanece válida durante toda a vida do objeto.
  • Ela não é um link público. Toda requisição passa por um proxy de autenticação que verifica se o solicitante está autenticado e possui acesso READ neste backend de dados — reverificado a cada requisição. Revogar o acesso de um usuário revoga imediatamente a capacidade dele de buscar qualquer arquivo.
  • Ela renderiza em um <img>. O navegador envia o seu cookie de sessão automaticamente, de modo que um widget de dashboard pode usar a URL em <img src>, <video src> ou em um link de download sem nenhum JavaScript envolvido.

Para entregar um arquivo a alguém fora do projeto, o share_url do SDK cunha, em vez disso, um link ao portador (bearer) com expiração — veja compartilhando objetos para saber quando usar cada um.

Arquivos Grandes

Transferências de até 6 MiB viajam como uma única chamada através do sistema de mensagens. Qualquer coisa maior é transportada diretamente entre o dispositivo e o armazenamento de objetos via HTTPS — o SDK alterna automaticamente, faz streaming de e para o disco, e um arquivo de múltiplos gigabytes nunca precisa caber na memória. O teto de upload único é de 5 GiB.

O caminho direto exige que o dispositivo alcance o host de armazenamento de objetos (s3.ironflock.com), não apenas o roteador de mensagens. Se um proxy de fábrica permitir apenas o roteador, as transferências grandes falham com o código explícito PRESIGN_UNREACHABLE em vez de um erro genérico — e um relógio de dispositivo defasado em mais de 15 minutos falha com CLOCK_SKEW, o que é um chamado para verificar o NTP, não as credenciais.

Compartilhando Arquivos Entre Apps

O acesso a arquivos entre apps se apoia no mesmo consentimento que o acesso a tabelas entre apps. Há um único interruptor: quando um usuário do projeto concede ao app B acesso aos dados do app A (o consentimento data_access nas configurações do app), essa concessão cobre as tabelas de A e os namespaces de arquivos não privados de A. Revogá-la revoga ambos.

O que o seu app controla como provedor é o flag private por namespace:

  • private: false (o padrão) — apps que possuem uma concessão de data-access podem lê-lo (nunca escrevê-lo).
  • private: true — o namespace é invisível para outros apps, ponto final, mesmo sob uma concessão.

Isso espelha exatamente consumindo dados de outros apps: namespaces e tabelas têm o mesmo padrão. Nada é compartilhado sem a concessão do usuário do projeto — private: apenas restringe o que um leitor já consentido enxerga, não é o consentimento em si.

A Visão do Usuário

Os usuários do projeto veem e governam o armazenamento do seu app em dois lugares:

  • A visão de Dados mostra uma entrada Arquivos ao lado das tabelas e views de cada app — uma listagem pesquisável de cada objeto armazenado com tamanho, tipo e data de modificação, e download por arquivo.
  • As configurações de armazenamento do app mostram o uso (bytes e contagem de objetos), o orçamento imposto ao lado da sugestão do seu app, um controle para alterar o orçamento e uma ação Excluir todos os arquivos — o gêmeo, do lado dos arquivos, de esvaziar todas as tabelas. A exclusão é confirmada digitando o nome do app e não pode ser desfeita.
  • O assistente de IA pode listar, pesquisar e ler esses arquivos em nome de um usuário. A mesma verificação DATABACKEND/READ se aplica, portanto ele nunca exibe um arquivo que o usuário não pudesse abrir por conta própria. A busca é feita nos caminhos dos arquivos, não no conteúdo, e ele lê arquivos de texto, imagens e PDFs — arquivos compactados e outros formatos binários ele não consegue ler.

Como acontece com as tabelas, estes são os dados do usuário: ele pode inspecioná-los, limitá-los e excluí-los sem envolver você.

Acesso Direto ao S3

Para tudo o que vai além do SDK — um analista com DuckDB, um backup noturno com rclone, um pipeline de BI — um usuário do projeto pode emitir credenciais S3 somente leitura por app a partir das configurações de armazenamento do app.

Cada credencial é restrita à área de armazenamento daquele único app: uma credencial emitida para o app A não funciona para os arquivos do app B, estruturalmente — a política de armazenamento de B simplesmente nunca a nomeia. Um projeto pode manter várias credenciais por app (uma por consumidor: um job de CI, um script de backup, um laptop), e revogar uma deixa as demais funcionando. Essa é também a história da rotação: se uma chave vazar, emita uma segunda credencial, migre o consumidor para ela, revogue a primeira — nenhum outro consumidor é perturbado.

O segredo é exibido uma única vez, na criação. A emissão requer acesso de leitura ao backend de dados desse app — a mesma permissão que permite ler os arquivos em primeiro lugar, de modo que a credencial nunca pode ampliar o alcance de ninguém.

# rclone rclone config create myapp s3 provider=Other \ endpoint=https://s3.ironflock.com \ access_key_id=ifs-key-3317-x7k2m secret_access_key=<shown once> rclone ls myapp:if-1042-3317
# DuckDB CREATE SECRET (TYPE S3, KEY_ID 'ifs-key-3317-x7k2m', SECRET '<shown once>', ENDPOINT 's3.ironflock.com'); SELECT * FROM read_parquet('s3://if-1042-3317/exports/*.parquet');

As configurações de armazenamento mostram o endpoint e o nome exato do bucket para o qual apontar um cliente. Note que uma listagem de buckets de nível superior (aws s3 ls sem argumento) não retorna nada por design — a credencial não possui nenhum bucket; a ela é concedido acesso a um. Enderece o bucket diretamente, como acima.

Appliances On-Premises

O armazenamento de arquivos funciona de forma idêntica em um appliance on-premises, com três diferenças que decorrem do design do appliance:

  • Os arquivos são servidos na mesma origem (same-origin) sob o próprio endereço do appliance (/files/...) — sem nome DNS extra, sem certificado extra, e funciona em instalações de HTTP puro. As URLs permanentes e o <img src> se comportam exatamente como na nuvem.
  • O backend de armazenamento do appliance faz streaming dos downloads de arquivos através da plataforma, em vez de redirecionar para um host de armazenamento separado, de modo que o serviço de armazenamento nunca é exposto como uma segunda origem.
  • Credenciais S3 diretas não estão disponíveis em appliances — o object store embarcado não consegue expressar concessões de acesso por credencial. A seção simplesmente não aparece nas configurações de armazenamento ali. Todo o resto, incluindo o comportamento de arquivos grandes através do SDK e a retenção declarada, funciona da mesma forma.

Um appliance isolado (air-gapped) serve todo o tráfego de arquivos localmente: logos de apps, imagens de dashboard e downloads de arquivos não precisam de conectividade com a internet.

Limites em Resumo

LimiteValorDe onde vem
Transferência inline (chamada única)6 MiBReportado em tempo de execução; pode ser elevado no lado do servidor
Objeto individual100 MiB por padrão, 5 GiB no máximomaxObjectBytes por namespace
Upload único5 GiBTeto de PUT único do object store; multipart ainda não está disponível
Orçamento de armazenamento1 GiB dev / 10 GiB prod por padrãoSugerido pelo template, decidido pelo usuário
Comprimento e caracteres da chaveUTF-8, caminhos separados por /../, caracteres de controle e prefixos reservados são rejeitados
Last updated on