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:
Python
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
- Opcionalmente, declare uma seção
files:em.ironflock/data-template.yml. - 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.
- O seu código de borda armazena e lê objetos através da API
filesdo SDK. - 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: trueNamespaces
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 namespacedefault. 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:
| Campo | Significado |
|---|---|
name | Letras minúsculas, dígitos e hifens, começando com uma letra. sys, system, ironflock e ironflock-* são reservados |
description | Exibida aos usuários e legível por agentes de IA |
private | Exclui o namespace do acesso entre apps. O padrão é false (compartilhado), exatamente como as tabelas |
contentTypes | Tipos MIME permitidos, globs são aceitos (image/*). Padrão: qualquer um |
maxObjectBytes | Maior objeto individual, até 5 GiB. Padrão de 100 MiB |
retention.deleteAfter | Objetos 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/READse 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
| Limite | Valor | De onde vem |
|---|---|---|
| Transferência inline (chamada única) | 6 MiB | Reportado em tempo de execução; pode ser elevado no lado do servidor |
| Objeto individual | 100 MiB por padrão, 5 GiB no máximo | maxObjectBytes por namespace |
| Upload único | 5 GiB | Teto de PUT único do object store; multipart ainda não está disponível |
| Orçamento de armazenamento | 1 GiB dev / 10 GiB prod por padrão | Sugerido pelo template, decidido pelo usuário |
| Comprimento e caracteres da chave | UTF-8, caminhos separados por / | ../, caracteres de controle e prefixos reservados são rejeitados |