OpenCode no Docker

há 1 semana 7

Saudações.

Hoje vou ensinar como enlatei o OpenCode em um container e criei um enxame de programadores automatizados.

Pré-requisitos:

  • Servidor, VPS ou VM com Linux;
  • Docker;

1 – Sobre o OpenCode

O OC é um agente de IA especializado e refinado para planejar (plan) e construir (build) softwares.

Ele é uma “versão gratuita e aberta do Claude Code” e se tornou muito famoso ser simples e poderoso.

Embora essa seja a principal função dele, você pode usá-lo como agente genérico para qualquer tarefa.

É normal rodar vários agentes ao mesmo tempo, basta que para cada projeto você abra uma tela no terminal, vá até a pasta do seu projeto e rode o comando “opencode“.

Ele é executado de duas formas:

  • Terminal do Agente: Ao rodar o comando “opencode” ele inicia um terminal moderno para interação com você, o usuário, nesse terminal você insere o prompt ou comandos específicos que iniciam com “/”;
  • Comando para o Agente: Permite acionar o comando “opencode” com argumentos específicos para tarefas onshot.

Para funcionar ele precisa:

  • Acesso a Internet durante a execução;
  • Chave de API para algum modelo de IA.

Links:

Eu particularmente não quero amarrá-lo ao meu notebook nem acessar um terminal gráfico remotamente. Ao enlatar o OpenCode em container eu ganho a liberdade de ter um container para cada projeto e acessá-lo puramente por SSH de qualquer lugar, alem de automatizar execuções com gatilhos próprios.

2 – OpenCode no Docker

Essa parte foi um pouco difícil. Por ser feito em Javascript/TypeScript e por padrão rodando no framework do nodejs o OpenCode espalha arquivos em vários lugares.

Publiquei a imagem no Docker Hub em tmsoftbrasil/opencode:latest, logo você pode pular esse capítulo.

Leia os tópicos abaixo para entender a engenharia da imagem.

2.1 – Caminhos e volumes

Esses caminhos precisam ser mapeados e direcionado para volumes, assim o container separa:

  • Programas: Os binários e bibliotecas de códigos;
  • Dados: Configurações e informações acumuladas pelo OC;
  • Cache: Arquivos gerados para acelerar a execução;
  • Workspace: Projeto de código onde serão produzidos os artefatos do agente, como códigos, documentações, etc;

Caminhos que configurei:

Caminho dentro do containerNaturezaPersistência recomendada
/data/config/opencodeConfig global, opencode.json, tui.json, agents, commands, plugins, skillsPersistente
/data/share/opencode/auth.jsonCredenciais, chaves de API, OAuthPersistente e sensível
/data/share/opencode/mcp-auth.jsonTokens OAuth de servidores MCPPersistente e sensível
/data/share/opencode/opencode.dbBanco SQLite observado por vocêPersistente
/data/share/opencode/logLogs do OpenCodePersistente por padrão
/data/home/opencodeHOME do usuário, fallback para ferramentas que gravam em ~Persistente
/data/npm-globalPacotes npm globais instalados em runtimePersistente
/cache/npmCache e logs do npmDescartável
/cache/xdg/opencodeCache XDG do OpenCodeDescartável
/cache/tmpTMPDIR e /tmp, inclusive .so temporárioDescartável
/cache/opencode-config-node_modulesnode_modules gerado em config/pluginsDescartável, recriável
/workspaceCódigo do projetoBind mount do host

2.2 – Arquivos para construir a imagem

Dockerfile da imagem:

Dockerfile – ./docker/Dockerfile

# syntax=docker/dockerfile:1.7 # Base Debian Linux FROM debian:bookworm-slim ARG APP_UID=1000 ARG APP_GID=1000 # Versao do OpenCode, usar latest ou versao especifica # docker buikd ... --build-arg OPENCODE_VERSION=1.x.y ARG OPENCODE_VERSION=latest ENV DEBIAN_FRONTEND=noninteractive \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 # Pacotes base: RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ bash \ ca-certificates \ curl \ wget \ git \ openssh-client \ nodejs \ npm \ brotli \ tar \ xz-utils \ zstd \ unzip \ tzdata \ openssl \ gosu \ less \ procps \ jq \ sqlite3 \ ripgrep \ ; \ rm -rf /var/lib/apt/lists/* # Instala OpenCode via npm RUN set -eux; \ npm install -g "opencode-ai@${OPENCODE_VERSION}"; \ opencode --version # Usuario não-root para rodar o agente. # A HOME real sera em /data, para persistir tudo que for config/ RUN \ set -eux; \ groupadd -g "${APP_GID}" opencode; \ useradd -u "${APP_UID}" -g "${APP_GID}" \ -d /data/home/opencode -s /bin/bash opencode; \ \ mkdir -p /data /cache /workspace /cache/tmp /root/.local; \ chmod 0755 /data /cache /workspace; \ chmod 1777 /cache/tmp # Redireciona /tmp para /cache/tmp, inclusive para arquivos .so temporarios. RUN \ rm -rf /tmp; \ ln -s /cache/tmp /tmp # Criar links de /root para os volumes caso rode processos como root dentro do container RUN \ rm -rf /root/.config /root/.cache /root/.npm /root/.local/share; \ ln -s /data/config /root/.config; \ ln -s /data/share /root/.local/share; \ ln -s /cache/xdg /root/.cache; \ ln -s /cache/npm /root/.npm # Instalar script de entrypoint COPY rootfs/opt/entrypoint.sh /opt/entrypoint.sh RUN chmod +x /opt/entrypoint.sh # Variaveis centrais: # - XDG_CONFIG_HOME e XDG_DATA_HOME em /data # - XDG_CACHE_HOME, npm cache e TMPDIR em /cache # - NO_PROXY para evitar que o servidor local do TUI passe por proxy ENV HOME=/data/home/opencode \ XDG_CONFIG_HOME=/data/config \ XDG_DATA_HOME=/data/share \ XDG_CACHE_HOME=/cache/xdg \ OPENCODE_CONFIG_DIR=/data/config/opencode \ NPM_CONFIG_CACHE=/cache/npm \ NPM_CONFIG_PREFIX=/data/npm-global \ TMPDIR=/cache/tmp \ NO_PROXY=localhost,127.0.0.1 \ PATH=/data/npm-global/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # Diretorio padrao inicial WORKDIR /workspace # Volumes anonimos (default, requer mapeamento no run) VOLUME ["/data", "/cache", "/workspace"] # Entrypoint ENTRYPOINT ["/opt/entrypoint.sh"] # Comando padrao CMD ["bash"]

Script de Entrypoint:

Shell Script rootfs/opt/entrypoint.sh

#!/bin/bash set -euo pipefail; # Env defaults export HOME="${HOME:-/data/home/opencode}"; export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-/data/config}"; export XDG_DATA_HOME="${XDG_DATA_HOME:-/data/share}"; export XDG_CACHE_HOME="${XDG_CACHE_HOME:-/cache/xdg}"; export OPENCODE_CONFIG_DIR="${OPENCODE_CONFIG_DIR:-/data/config/opencode}"; export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-/cache/npm}"; export NPM_CONFIG_PREFIX="${NPM_CONFIG_PREFIX:-/data/npm-global}"; export TMPDIR="${TMPDIR:-/cache/tmp}"; export NO_PROXY="${NO_PROXY:-localhost,127.0.0.1}"; # Criar link simbolico safe_link() { local src="$1"; local dst="$2"; if [ -L "$dst" ] || [ ! -e "$dst" ]; then ln -sfnT "$src" "$dst"; fi; }; # Garantir diretorios usados mkdir -p \ "$HOME" \ "$HOME/.local" \ "$XDG_CONFIG_HOME/opencode" \ "$XDG_DATA_HOME/opencode" \ "$XDG_CACHE_HOME/opencode" \ "$NPM_CONFIG_CACHE" \ "$NPM_CONFIG_PREFIX/bin" \ "$TMPDIR" \ /cache/opencode-config-node_modules \ /workspace; # Ajustes de permissoes chmod 700 "$XDG_DATA_HOME/opencode" || true; chmod 1777 "$TMPDIR" || true; # Compatibilidade para softwares que ignoram variáveis XDG. safe_link "$XDG_CONFIG_HOME" "$HOME/.config" safe_link "$XDG_DATA_HOME" "$HOME/.local/share" safe_link "$XDG_CACHE_HOME" "$HOME/.cache" safe_link "$NPM_CONFIG_CACHE" "$HOME/.npm" # Classificacao deliberada: # ~/.config/opencode e' /data, mas node_modules gerado por # plugins/config pode ir para /cache. # Se preferir preservar node_modules também, remova este bloco. if [ ! -e "$XDG_CONFIG_HOME/opencode/node_modules" ]; then ln -s \ /cache/opencode-config-node_modules \ "$XDG_CONFIG_HOME/opencode/node_modules"; fi; # Config inicial editavel. Desativa autoupdate dentro do container: # container deve ser atualizado via rebuild da imagem, # não mutando binários em runtime. if [ "${OPENCODE_INIT_CONFIG:-1}" = "1" ]; then if [ ! -e "$XDG_CONFIG_HOME/opencode/opencode.json" ]; then ( echo '{'; echo ' "$schema": "https://opencode.ai/config.json",'; echo ' "autoupdate": false'; echo '}'; ) > $XDG_CONFIG_HOME/opencode/opencode.json; fi; fi; # Se entrou como root, ajusta somente /data e /cache e # depois cai para usuário não-root. # Não faz chown de /workspace para não # alterar dono dos arquivos do projeto no host. if [ "$(id -u)" = "0" ]; then if [ "${OPENCODE_CHOWN:-1}" = "1" ]; then chown -R opencode:opencode /data /cache fi exec gosu opencode "$@" fi # Rodar CMD do container exec "$@";

2.3 – Construção da imagem

Para construir a imagem, crie uma pasta para esse projeto “opencode-builder” crie os dois arquivos (conteúdo acima) nos seguintes caminhos:

  • Dockerfile: ./docker/Dockerfile
  • Entrypoint: ./rootfs/opt/entrypoint.sh

Execute o build para construir a imagem:

Bash

# Constuir imagem opencode:latest docker build . -f docker/Dockerfile --no-cache -t opencode:latest;

Caso queira construir uma imagem versionada usando uma versão específica do OpenCode:

Bash

# Construir imagem de versao especifica do opencode: OPENCODE_VERSION="1.x.y"; docker build . \ -f docker/Dockerfile\ -t opencode:$OPENCODE_VERSION \ --build-arg OPENCODE_VERSION=$OPENCODE_VERSION;

3 – Rodando OpenCode no Docker

Vamos rodar o OC no Docker, criando o container opencode-dev.

2.1 – Rede Docker

Criando a rede para containers (network_public):

Bash

# Rede de containers docker network create network_public \ -d bridge \ -o com.docker.network.bridge.name=br-net-public \ -o com.docker.network.driver.mtu=1500 \ -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected \ --subnet 10.249.0.0/16 \ --gateway 10.249.255.254;

2.2 – Volume

A pasta /workspace dentro do container deve ser mapeada na pasta onde estará seu projeto de software no HOST.

Ela é a pasta onde os programas são executados por padrão (workdir do container).

Nesse exemplo vou armazenar tudo em /storage/opencode-dev para você testar e aprender, e depois você personaliza.

Bash

# Diretorio do volume no HOST DATADIR=/storage/$NAME; # Pastas de volumes montados na pasta principal do volume no host mkdir -p $DATADIR; # - Configs e DB de contexto mkdir -p $DATADIR/data; # - Cache do bibliotecas mkdir -p $DATADIR/cache; # - Pasta com projeto de software (caminho padrao inicial /workspace) mkdir -p $DATADIR/workspace;

2.3 – Container do OC

Comando para rodar no “docker run”:

Bash

# Variaveis NAME="opencode-dev"; # Para usar sua imagem local construida no cap. 2 #IMAGE="opencode:latest"; # Para usar minha imagem pronta: IMAGE="tmsoftbrasil/opencode:latest"; # Diretorio de dados persistentes: DATADIR=/storage/$NAME; # Pastas de volumes montados # na pasta principal do volume no host mkdir -p $DATADIR; mkdir -p $DATADIR/data; mkdir -p $DATADIR/cache; mkdir -p $DATADIR/workspace; # Rodar container # Renovar/rodar docker rm -f $NAME 2>/dev/null # --read-only docker run \ -d --restart=always \ --name $NAME --hostname $LOCAL.intranet.br \ \ --read-only \ --tmpfs /run:rw,noexec,nosuid,size=16m \ --tmpfs /tmp:rw,noexec,nosuid,size=16m \ \ --cpus=2 \ --memory 2g --memory-swap 2g --memory-reservation 256m \ \ --network network_public \ \ -v $DATADIR/data:/data \ -v $DATADIR/cache:/cache \ -v $DATADIR/workspace:/workspace \ \ \ $IMAGE \ tail -f /dev/null;

2.4 – Stack para Compose

Caso prefira no modelo de Stack para docker compose:

YAML

name: opencode-dev services: opencode-dev: image: tmsoftbrasil/opencode:latest container_name: opencode-dev hostname: opencode-dev restart: always read_only: true command: tail -f /dev/null tmpfs: - /run:rw,noexec,nosuid,size=16m - /tmp:rw,noexec,nosuid,size=16m deploy: resources: limits: cpus: "2" memory: 2g reservations: memory: 256m mem_swappiness: 0 memswap_limit: 2g networks: - network_public volumes: - /storage/opencode-dev/data:/data - /storage/opencode-dev/cache:/cache - /storage/opencode-dev/workspace:/workspace networks: network_public: external: true

3 – Primeiro acesso

Obtenha shell no container “opencode-dev“:

Bash

# Obter shell no container: docker exec -it opencode-dev bash; # Rodar opencode: opencode; # Ou, rodar direto o opencode dentro do container: docker exec -it opencode-dev opencode;

3.1 – Tela inicial

Você verá a seguinte tela:

3.2 – Conectando OC a um modelo de IA

O primeiro passo é fornecer um modelo usando o comando “/connect“.

Gratuitamente: Escolha “OpenCode Zen“, acesse https://opencode.ai/zen para obter uma chave, você pode escolher algum modelo “Free” para usar se precisar pagar nada.

Pagos e baratos: Escolha “OpenRouter“, acesse https://openrouter.ai, cadastre-se, coloque algum crédito (15 dólares), recomendo usar o DeepSeek V4 ou algum modelo focado em código com o preço baixo.

Eu uso OpenRouter com DeepSeek V4 PRO. Custa alguns centavos de dolar por hora e produz software com muita precisão.

Após rodar o /connect escolhe OpenRouter:

Inserindo a chave de API gerada no site:

Escolha o modelo:

E por fim, pronto para usar:

Para fechar o OpenCode (não para o container):

3.3 – Criando alguma coisa com IA

Com o OpenCode aberto, basta pedir alguma coisa, o resultado por padrão será gerado em /workspace (dentro do container) que fica na pasta do HOST onde esse volume foi mapeado.

Prompt:

Agente OC trabalho:

Resultado pronto:

Resultado (abri arquivo aviso.html no navegador):

.

Agora é contigo. Use sua criatividade.

Sábio não é aquele que sabe
de tudo e sim usa tudo que sabe
Provérbio Chinês

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com