Ativos persistentes (`assets:`)

O campo assets: organiza os dados persistentes da app em quatro categorias semânticas — secrets, state, cache e config. É açúcar tipado sobre volumes: (que continua válido como fallback low-level): cada categoria define o modo de montagem, a localização no host e as regras de ciclo de vida.

volumes: continua funcionando. assets: é opt-in — você adota apenas os assets que faz sentido tipar.

Pré-requisitos

  • Runner v2.37.0 ou superior
  • App registrada e com .deploy.yml no repo

As quatro categorias

assets:
  secrets:                       # Operador sobe 1×; montado :ro no container
    - source: poc/secrets        # host: <app>/data/poc/secrets
      mount: /app/poc/secrets
      required: true             # deploy aborta se dir ausente/vazio
      docs: "Coloque oauth-token.json + oauth-client.json aqui."

  state:                         # App escreve; persiste entre versões; :rw
    - source: poc/out
      mount: /app/poc/out
      backup: daily              # metadado para BackupAlly (runner não agenda)

  cache:                         # Ephemeral/rebuildável; :rw
    - source: cache/next
      mount: /root/.cache
      ttl: 30d                   # metadado para `assets clean` (Fase 2 — v2.38.0)

  config:                        # Arquivo do repo na versão deployada; :ro
    - source_in_repo: livekit.prod.yaml
      mount: /etc/livekit.yaml
Categoria Modo Quem escreve Atualiza no deploy
secrets :ro Operador (upload manual) Não
state :rw App em runtime Não (persiste entre versões)
cache :rw App em runtime Não (rebuildável)
config :ro Repo (vem do código) Sim (sempre a versão atual)

Layout no host

Todos os assets (exceto config) vivem sob <app>/data/<source>:

/data/apps/<app>/
├── data/
│   ├── poc/secrets/        ← assets.secrets (source: poc/secrets)
│   ├── poc/out/            ← assets.state   (source: poc/out)
│   └── cache/next/         ← assets.cache   (source: cache/next)
├── src/<instance>/<ver>/
│   └── livekit.prod.yaml   ← assets.config  (source_in_repo: livekit.prod.yaml)
├── logs/
└── .runner/secrets.enc

A categoria config é montada direto da versão deployada no repositório (src/<instance>/<versão>/) — o arquivo é atualizado a cada deploy.

Validação `required:` — aborta antes de qualquer container

Quando required: true, o runner verifica no pré-flight que o dir <app>/data/<source> existe e não está vazio. Se estiver ausente ou vazio, o deploy é abortado com a mensagem do campo docs::

[asset_required_missing] asset 'poc/secrets' (secrets) está vazio/ausente.
  Coloque oauth-token.json + oauth-client.json aqui.
  Path: /data/apps/capital28-dashboard/data/poc/secrets

A verificação ocorre após o refresh do manifesto (pós-fetch/git pull) e antes de qualquer ação de container — não há rollback necessário porque nenhum container foi tocado.

required: é ignorado (com aviso de validação) em cache, pois cache é ephemeral por definição.

Como popular um asset de secrets antes do primeiro deploy:

mkdir -p /data/apps/<app>/data/poc/secrets
cp oauth-token.json oauth-client.json /data/apps/<app>/data/poc/secrets/
runner deploy <app>   # agora o required passa

Chown automático para imagens non-root

Ao criar um diretório de state ou cache pela primeira vez, o runner inspeciona o campo Config.User da imagem (via docker inspect) e executa chown -R <user> <dir> se o user for não-root. Isso garante que a app consiga escrever nos diretórios sem precisar de configuração extra.

  • secrets não recebe chown de escrita (montado :ro).
  • Se o chown falhar (ex: user nominal não resolvível no host), o runner emite WARN e prossegue — o deploy não é abortado.
  • O chown é aplicado apenas na criação do diretório (idempotente em runs seguintes).

Mounts automáticos: `data`, `logs` e `keys`

Para deploys do tipo docker-build, o runner monta automaticamente três diretórios do host quando eles existem, sem precisar declará-los:

Dir no host Mount no container Modo
<app>/data/ /app/data :rw
<app>/logs/ /app/logs :rw
<app>/keys/ /app/keys :ro

Basta criar o diretório no host para ativar o auto-mount:

mkdir -p /data/apps/<app>/logs   # a partir do próximo deploy, /app/logs fica montado

Se você declarar um asset ou volumes: com o mesmo destino (/app/data, /app/logs ou /app/keys), o auto-mount é suprimido para esse diretório — não há duplicidade.

Campos `backup:` e `ttl:` são apenas metadados

O runner não agenda backups nem expira cache automaticamente:

  • backup: — metadado lido pelo BackupAlly para definir a frequência de snapshot dos dados da app.
  • ttl: — será usado pelo comando runner assets clean na Fase 2 (v2.38.0).

Tabela de ciclo de vida

O que cada comando preserva ou remove nos artefatos da app:

Comando src/<ver>/ data/ (assets) logs/ Named volumes .runner/secrets.enc
deploy recriado/LRU (keep_versions) preservado preservado preservado preservado
rollback symlink aponta para versão anterior preservado preservado preservado preservado
archive contêineres parados; dirs preservados preservado preservado preservado preservado
unregister removido REMOVIDO REMOVIDO preservado (Docker) removido
reset (soft) versões e symlink removidos preservado preservado preservado preservado
reset --hard src/<instance>/ inteiro removido preservado preservado preservado .env/.secrets regenerados do manifesto

Atenção: `unregister` remove tudo

unregister executa remove_dir_all no diretório da app — isso apaga data/, logs/, .runner/ e tudo mais. Named volumes Docker são gerenciados pelo próprio Docker e não são removidos.

Antes de unregister, faça snapshot dos assets:

# Fase 2 (v2.38.0) terá `runner assets snapshot <app>`.
# Por ora, backup manual:
tar -C /data/apps/<app> -czf /tmp/<app>-assets-backup.tar.gz data logs
runner unregister <app>

`reset --hard` preserva os dados

Ao contrário do que o --help sugere ("wipe .env, .secrets, and persistent_dirs"), o --hard remove apenas src/<instance>/ (a árvore de versões do deploy). data/, logs/ e .runner/secrets.enc são preservados. O que acontece com .env/.secrets é regeneração: o runner re-executa o wizard com os valores declarados no manifesto.

Use unregister seguido de re-registro se precisar começar do zero completamente.

Validação de schema

O runner valida as entradas de assets: no runner validate:

Regra Erro
source absoluto ou com .. asset_source_invalid
mount relativo (não começa com /) asset_mount_invalid
mount igual a outro asset ou volumes: asset_mount_collision
required: true em cache warning asset_required_on_cache
required: true e dir vazio no deploy asset_required_missing (aborta)

Coexistência com `volumes:`

assets: e volumes: coexistem no mesmo manifesto. Use volumes: para casos que não se encaixam nas categorias (ex: named volumes compartilhados entre apps, mounts absolutos de paths fixos do servidor):

assets:
  secrets:
    - source: credentials
      mount: /app/credentials
      required: true

volumes:
  - shared-queue:/app/queue:rw     # named volume compartilhado com outra app
  - /srv/certs:/app/certs:ro       # path absoluto do servidor

Mounts declarados em volumes: entram no conjunto de destinos explícitos — o runner não duplica auto-mounts para eles.

By Borlot.com.br on 10/06/2026