Coder IDE no Docker

Saudações.

Hoje vou ensinar como enlatei o Coder dentro de um container Docker com vários agentes de IA.

Pré-requisitos:

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

1 – Sobre o Coder

O Coder é uma IDE de desenvolvimento que funciona no navegador.

Ele é inspirado e compatível com o VSCode.

Depois de um tempo tendo que lidar com gestão de projetos usando o método tradicional de instalar tudo no notebook e as vezes sincronizar com o Git, me veio uma ideia: E se a IDE, o projeto, a compilação, testes, agentes de IA pudessem ficar enlatados no Docker?

Com isso eu poderia programar em qualquer computador, smartphone, tablet, delegar tarefas aos agentes, sem precisar manter nenhum deles ligados.

Links:

2 – Guia rápido

Para quem tem pressa!

Crie a rede Docker:

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;

Criar volume e configuração inicial com senha tulipa_coder (mude para a sua):

Bash
# Diretorio do volume
    # Pasta no HOST
    mkdir -p /storage/coder-ide;

    # Pasta do projeto para mapear em /project do container
    mkdir -p /storage/coder-ide/project;

    # Raiz do sistema
    mkdir -p /storage/coder-ide/.config/code-server;

    # Config inicial
    #(
    #    echo;
    #    echo 'bind-addr: 127.0.0.1:8080';
    #    echo 'auth: password';
    #    echo 'password: tulipa_coder';
    #    echo 'cert: false';
    #    echo;
    #) > /storage/coder-ide/.config/code-server/config.yaml;

    # Ajustar permissoes, login 'coder' uid 1000
    chown -R 1000:1000 /storage/coder-ide;

Container do Coder:

Bash
# Senha de acesso
    PASSWORD="tulipa_coder";

# Atualizar imagem
    docker pull codercom/code-server:latest;

# Renovar/rodar:
    # - Parar atual
    docker container  stop  coder-ide 2>/dev/null;
    docker container  rm    coder-ide 2>/dev/null;

    # - Rodar com imagem renovada
    docker container run \
        -d \
        --user               root \
        --restart            always \
        --name               coder-ide \
        --hostname           coder-ide.intranet.br \
        \
        --cpus                2.0  \
        --cpu-shares         1024 \
        --memory               2g \
        --memory-swap          2g \
        --memory-reservation   1g \
        --shm-size             1g \
        \
        --network            network_public \
        --ip                 10.249.111.201 \
        \
        -p 7280:8080 \
        \
        -v /storage/coder-ide:/root \
        -v /storage/coder-ide:/home/coder \
        -v /storage/coder-ide/project:/project \
        \
        -v /var/run/docker.sock:/var/run/docker.sock \
        \
        -e DEFAULT_WORKSPACE=/project \
        -e DOCKER_USER=root \
        -e PASSWORD=$PASSWORD \
        \
        codercom/code-server:latest \
            --auth password \
            /project;

        # entrypoint: /usr/bin/entrypoint.sh
        # cmd.......: --bind-addr 0.0.0.0:8080

Versão Docker Compose:

YAML – docker-compose.yml
services:
  coder-ide:
    image: codercom/code-server:latest
    container_name: coder-ide
    hostname: coder-ide.intranet.br
    user: root
    restart: always
    networks:
      network_public:
        ipv4_address: 10.249.111.201
    ports:
      - "7280:8080"
    volumes:
      - /storage/coder-ide:/root
      - /storage/coder-ide:/home/coder
      - /storage/coder-ide/project:/project
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      DEFAULT_WORKSPACE: /project
      DOCKER_USER: root
      PASSWORD: tulipa_coder
    command:
      - --auth
      - password
      - /project
    cpus: 2.0
    cpu_shares: 1024
    mem_limit: 2g
    memswap_limit: 2g
    mem_reservation: 1g
    shm_size: 1g

networks:
  network_public:
    name: network_public
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br-net-public
      com.docker.network.driver.mtu: 1500
      com.docker.network.bridge.gateway_mode_ipv4: nat-unprotected
    ipam:
      config:
        - subnet: 10.249.0.0/16
          gateway: 10.249.255.254

Primeiro acesso: Abra o navegador no endereço do servidor porta HTTP 7280.

x

x

3 – Guia manual

Nesse capítulo vou mostrar como eu fiz para criar um bundle de desenvolvimento no container.

3.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;

3.3 – Containers do Coder

Criando o container do Coder para instalar manualmente os agentes. Mais tarde vamos transformar esses comandos em Dockerfile.

Opcionalmente mapeie o volume para o socket do Docker para que você possa fazer testes em containers durante o desenvolvimento.

Bash
# Senha de acesso
    PASSWORD="tulipa_coder";

# Diretorio do volume
    # Pasta no HOST
    mkdir -p /storage/coder-ide;
    # Pasta do projeto para mapear em /project do container
    mkdir -p /storage/coder-ide/project;
    # Raiz do sistema
    mkdir -p /storage/coder-ide/.config/code-server;
    # Ajustar permissoes, login 'coder' uid 1000
    chown -R 1000:1000 /storage/coder-ide;

# Renovar/rodar:
    # - Parar atual
    docker container  stop  coder-ide 2>/dev/null;
    docker container  rm    coder-ide 2>/dev/null;

    # - Rodar com imagem renovada
    docker container run \
        -d \
        --user               root \
        --restart            always \
        --name               coder-ide \
        --hostname           coder-ide.intranet.br \
        \
        --cpus                2.0  \
        --cpu-shares         1024 \
        --memory               2g \
        --memory-swap          2g \
        --memory-reservation   1g \
        --shm-size             1g \
        \
        --network            network_public \
        --ip                 10.249.111.201 \
        \
        -p 7280:8080 \
        \
        -v /storage/coder-ide:/root \
        -v /storage/coder-ide:/home/coder \
        -v /storage/coder-ide/project:/project \
        \
        -v /var/run/docker.sock:/var/run/docker.sock \
        \
        -e DEFAULT_WORKSPACE=/project \
        -e DOCKER_USER=root \
        -e PASSWORD=$PASSWORD \
        \
        codercom/code-server:latest \
            --auth password \
            /project;

Vamos preparar ambiente com ferramentas básicas, entre no container:

Bash
# Entrar no shell do container:
docker exec -it --user root coder-ide bash;

E instale os pacotes básicos (pacotes presentes em qualquer notebook):

Bash – Container coder-ide (Alpine)
# Atualizar
apt -y update;
apt -y upgrade;
apt -y dist-upgrade;
apt -y full-upgrade;

# Pacotes nativos do Debian, mas que devem estar presentes,
# rode esse comando para garantir que nao faltou nenhum pacote basico:
apt -y install \
    bash sudo \
    openssl wget curl ca-certificates \
    iproute2 htop iputils-ping \
    bzip2 tar unzip gzip xz-utils zstd zip \
    util-linux coreutils procps psmisc \
    less logrotate lsof \
    sed grep mawk \
    mc nano jq git \
    file findutils \
    iputils-ping traceroute \
    openssh-client;

# SSH
apt -y install rsync;
apt -y install openssh-server;
apt -y install openssh-sftp-server;

# Instalar cliente Docker
apt -y install docker-cli;
apt -y install docker-compose;
apt -y install docker-buildx;

3.4 – Instalando agentes

Essa é a parte boa!

Existem duas formas de fazer isso:

  • Agente isolado: Você roda o agente em outro container Docker com acesso ao mesmo diretório do volume do seu projeto (/project), considero isso mais limpo e objetivo. Recomendado para ambientes profissionais. O principal vantagem é não embolar dependências (alguns precisam de node 22, outros de node 24, … vira bagunça).
  • Container bundle: Você instala os agentes no mesmo container. A desvantagem é que você precisará gerir bem os volumes usados pelo Coder e pelo Agente para que seu container não destrua configurações e dados ao ser recriado no futuro por ter acumulado dados em pastas dentro do container que não estavam mapeadas fora dele.

Vou fazer o bundle já que rodar isolado é matéria de outro artigo (OpenCode, Pi, …).

Instalando pacotes necessários para rodar os agentes no mesmo container:

Bash – Container coder-ide (Debian)
# Instalar nodejs e npm
apt -y install nodejs npm;

# Instalar Claude Code
npm install -g @anthropic-ai/claude-code;

# Instalar OpenCode
npm install -g opencode-ai;

# Instalando OpenClaude
npm install -g @gitlawb/openclaude@latest;

# Instalando Agent Pi (bugou comigo)
# npm install -g --ignore-scripts @earendil-works/pi-coding-agent;

Pronto pra usar na porta HTTP 7280.

4 – Criando o container pacotão

Agora vou criar uma única imagem juntando tudo que foi feito no capítulo 3.

Crie e entre na pasta coder-bundle:

Script – entrypoint.sh
mkdir -p /root/coder-bundle;
cd       /root/coder-bundle;

Nessa pasta coloque todos os arquivos abaixo:

  • supervisord.conf
  • sshd_config
  • crontab
  • entrypoint.sh
  • Dockerfile

4.1 – Supervisor

Usamos o supervidord para rodar vários softwares no container. Ele será o PID 1 que manterá e reiniciará todos os softwares caso falhem.

Arquivo supervisord.conf

Config – supervisord.conf
[unix_http_server]
file            = /var/run/supervisor.sock
chmod           = 0700

[supervisord]
logfile         = /root/log/supervisord.log
pidfile         = /run/supervisord.pid
childlogdir     = /root/log


[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl       = unix:///run/supervisor.sock

[program:sshd]
user            = root
command         = /usr/sbin/sshd -D
priority        = 1
directory       = /run
autostart       = true
autorestart     = true
startsecs       = 0
stopwaitsecs    = 2
stdout_logfile  = /root/log/service-%(program_name)s.log
stderr_logfile  = /root/log/service-%(program_name)s.err

[program:coder-server]
user            = root
command         = /usr/bin/entrypoint.sh --bind-addr 0.0.0.0:8080 --auth password /project
priority        = 2
directory       = /run
autostart       = true
autorestart     = true
startsecs       = 0
stopwaitsecs    = 2
stdout_logfile  = /root/log/service-%(program_name)s.log
stderr_logfile  = /root/log/service-%(program_name)s.err

[program:cron]
user            = root
command         = /usr/sbin/cron -f -L 8
priority        = 3
directory       = /run
autostart       = true
autorestart     = true
startsecs       = 0
stopwaitsecs    = 2
stdout_logfile  = /root/log/service-%(program_name)s.log
stderr_logfile  = /root/log/service-%(program_name)s.err

4.2 – Configuração SSH

Para permitir acesso direto no container por SSH temos que salvar as chaves do servidor em volume e autorizar a entrada como root direto.

O container não possui senha no usuário root, você deverá cadastrar sua chave pública no arquivo trusted_keys:

  • Dentro do container: /root/ssh/trusted_keys
  • No volume: /storage/coder-ide/ssh/trusted_keys

Arquivo sshd_config

Config – sshd_config
VersionAddendum                OpenSSH_12

Protocol                       2
Port                           22
AddressFamily                  any
ListenAddress                  0.0.0.0
ListenAddress                  ::
IPQoS                          lowdelay throughput
Compression                    yes
UseDNS                         no

ClientAliveInterval            3
ClientAliveCountMax            15
TCPKeepAlive                   yes

PubkeyAuthentication           yes
AuthorizedKeysFile	           /root/ssh/trusted_keys .ssh/authorized_keys

HostKey                        /root/ssh/ssh_host_rsa_key
HostKey                        /root/ssh/ssh_host_ecdsa_key
HostKey                        /root/ssh/ssh_host_ed25519_key

PasswordAuthentication         yes
PermitEmptyPasswords           no
PermitListen                   any
PermitRootLogin                yes
KbdInteractiveAuthentication   no
UsePAM                         yes
SyslogFacility                 AUTH
LogLevel                       INFO

PubkeyAuthentication           yes
RekeyLimit                     1G 1h
RequiredRSASize                2048

PrintLastLog                   no
PrintMotd                      no
AcceptEnv                      LANG LC_* COLORTERM NO_COLOR
Subsystem	                     sftp /usr/lib/openssh/sftp-server

AllowAgentForwarding           yes
AllowTcpForwarding             yes
X11Forwarding                  yes
X11DisplayOffset               10
GatewayPorts                   clientspecified
PermitTTY                      yes
PermitTunnel                   no
PermitUserEnvironment          no

Include                        /etc/ssh/sshd_config.d/*.conf

4.3 – Crontab

O plano de crontab com intervalos pre-configurados nos permitirá criar e acionar scripts de automação dentro do nosso projeto.

Vou criar em /root/cron/ pois a pasta /root ficará em volume, preservando assim o projeto e os scripts de automação juntos.

Arquivo crontab

Config – crontab

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# min   hour day month week-day user  program    args...
   0      *    *    *     *     root  run-parts  --regex '.*'  /root/cron/hourly
   0      2    *    *     *     root  run-parts  --regex '.*'  /root/cron/daily
   0      3    *    *     6     root  run-parts  --regex '.*'  /root/cron/weekly
   0      5    1    *     *     root  run-parts  --regex '.*'  /root/cron/monthly
 */1      *    *    *     *     root  run-parts  --regex '.*'  /root/cron/1min
 */5      *    *    *     *     root  run-parts  --regex '.*'  /root/cron/5min
*/10      *    *    *     *     root  run-parts  --regex '.*'  /root/cron/10min
*/15      *    *    *     *     root  run-parts  --regex '.*'  /root/cron/15min
*/30      *    *    *     *     root  run-parts  --regex '.*'  /root/cron/30min
   0      0    *    *     0     root  run-parts  --regex '.*'  /root/cron/sunday
   0      0    *    *     1     root  run-parts  --regex '.*'  /root/cron/monday
   0      0    *    *     2     root  run-parts  --regex '.*'  /root/cron/tuesday
   0      0    *    *     3     root  run-parts  --regex '.*'  /root/cron/wednesday
   0      0    *    *     4     root  run-parts  --regex '.*'  /root/cron/thursday
   0      0    *    *     5     root  run-parts  --regex '.*'  /root/cron/friday
   0      0    *    *     6     root  run-parts  --regex '.*'  /root/cron.saturday

4.4 – Entrypoint

Vou criar um novo entrypoint com supervidord para rodar o Coder Server e o OpenSSH Server, dessa forma poderemos entrar via SSH direto no container.

Arquivo entrypoint.sh

Script – entrypoint.sh
#!/bin/sh

# Garantir existencia dos diretorios
    mkdir -p /root/log;
    mkdir -p /run;
    mkdir -p /project;

# Preparativos do supervisord
    touch /root/log/supervisord.log;

# Preparativos do servidor SSH
    # Pasta de configs e chaves do servidor
    mkdir -p /root/ssh;

    # Pasta de configs e chaves do usuario
    mkdir -p /root/.ssh;

    # Arquivo de cadastro de cahves conviaveis dos clientes
    touch /root/ssh/trusted_keys;
    touch /root/.ssh/authorized_keys;

    # Chaves do servidor SSH
    [ -s /root/ssh/ssh_host_rsa_key ] || {
        ssh-keygen -t rsa   -b 4096 -f /root/ssh/ssh_host_rsa_key -N "";
        chmod 600 /root/ssh/ssh_host_rsa_key;
    };
    [ -s /root/ssh/ssh_host_ecdsa_key ] || {
        ssh-keygen -t ecdsa -b 521  -f /root/ssh/ssh_host_ecdsa_key -N "";
        chmod 600 /root/ssh/ssh_host_ecdsa_key;
    };
    [ -s /root/ssh/ssh_host_ed25519_key ] || {
        ssh-keygen -t ed25519       -f /root/ssh/ssh_host_ed25519_key -N "";
        chmod 600 /root/ssh/ssh_host_ed25519_key;
    };

    # Chave de usuario local pre-provisionada
    [ -s /root/.ssh/id_ed25519 ] || {
        ssh-keygen -t ed25519       -f /root/.ssh/id_ed25519 -N "";
        chmod 600 /root/.ssh/id_ed25519;
    };

# Preparativos do crontab
    crondirs="
        1min 10min 15min 30min
        hourly daily weekly monthly
        sunday monday tuesday wednesday
        thursday friday saturday
    ";
    for cdir in $crondirs; do
        mkdir -p /root/cron/$cdir;
    done;

# Comando
    EXEC_CMD="$@"

    # Rodar CMD
    if [ "x$EXEC_CMD" = "x" ]; then
        FULLCMD="exec sleep 252288000";
    else
        FULLCMD="exec $EXEC_CMD";
    fi;

    echo "Executando: $FULLCMD";
    eval $FULLCMD;

4.5 – Dockerfile

Construtor da imagem.

Arquivo Dockerfile

Dockerfile – Dockerfile
# syntax=docker/dockerfile:1.7

# Coder Server pronto, continuando
FROM        codercom/code-server:latest
USER        root

ENV \
            LANG=C.UTF-8 \
            LC_ALL=C.UTF-8

# Instalar pacotes basicos
RUN         set -eux; \
            apt -y update; \
            apt -y upgrade; \
            apt -y dist-upgrade; \
            apt -y full-upgrade; \
            \
            apt -y install \
                bash sudo \
                openssl wget curl ca-certificates \
                iproute2 htop iputils-ping \
                bzip2 tar unzip gzip xz-utils zstd zip \
                util-linux coreutils procps psmisc \
                less logrotate lsof \
                sed grep mawk \
                mc nano jq git \
                file findutils \
                iputils-ping traceroute \
                supervisor

# Instalar cliente Docker
RUN         apt -y install docker-cli \
                docker-compose \
                docker-buildx

# Instalar servidor SSH para acesso remoto
RUN         apt -y install \
                rsync \
                openssh-server \
                openssh-sftp-server \
                openssh-client

# Instalar nodejs
RUN         apt -y install \
                nodejs \
                npm

# Instalar claude-code
RUN         npm install -g @anthropic-ai/claude-code

# Instalar opencode
RUN         npm install -g opencode-ai

# Instalar openclaude
RUN         npm install -g @gitlawb/openclaude@latest

# Configs personalizadas
COPY        supervisord.conf  /etc/supervisord.conf
COPY        sshd_config       /etc/ssh/sshd_config
COPY        crontab           /etc/crontab

# Script de entrypoint
COPY        entrypoint.sh     /opt/entrypoint.sh

# Finalizacao e arremate
RUN         chmod  +x         /opt/entrypoint.sh

# Diretorio padrao inicial
WORKDIR     /project

# Entrypoint para supervidord
# /opt/entrypoint.sh /usr/bin/supervisord -n -c /etc/supervisord.conf
ENTRYPOINT  ["/opt/entrypoint.sh"]
CMD         ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf"]

4.6 – Construção da imagem

Execute o build para construir a imagem:

Bash
# Constuir imagem:
docker build . \
    -f Dockerfile \
    -t coder-bundle:latest;

4.7 – Rodando pacotão

Agora basta usar a imagem coder-bundle:latest para ter um super ambiente de desenvolvimento isolado, na nuvem, cheio de bots trabalhadores.

Bash
# Senha de acesso
    PASSWORD="tulipa_coder";

# Diretorio do volume
    # Pasta no HOST
    mkdir -p /storage/coder-ide;
    # Pasta do projeto para mapear em /project do container
    mkdir -p /storage/coder-ide/project;
    # Raiz do sistema
    mkdir -p /storage/coder-ide/.config/code-server;
    # Ajustar permissoes, login 'coder' uid 1000
    chown -R 1000:1000 /storage/coder-ide;

# Renovar/rodar:
    # - Parar atual
    docker container  stop  coder-ide 2>/dev/null;
    docker container  rm    coder-ide 2>/dev/null;

    # - Rodar com imagem renovada
    docker container run \
        -d \
        --user               root \
        --restart            always \
        --name               coder-ide \
        --hostname           coder-ide.intranet.br \
        \
        --cpus                2.0  \
        --cpu-shares         1024 \
        --memory               2g \
        --memory-swap          2g \
        --memory-reservation   1g \
        --shm-size             1g \
        \
        --network            network_public \
        --ip                 10.249.111.201 \
        \
        -p 7422:22   \
        -p 7480:8080 \
        \
        -v /storage/coder-ide:/root \
        -v /storage/coder-ide:/home/coder \
        -v /storage/coder-ide/project:/project \
        \
        -v /var/run/docker.sock:/var/run/docker.sock \
        \
        -e DEFAULT_WORKSPACE=/project \
        -e DOCKER_USER=root \
        -e PASSWORD=$PASSWORD \
        \
        coder-bundle:latest;

O acesso do container acima está na porta HTTP 7422, o acesso pode ser feito por SSH, porta externa SSH 7422.

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com

Uma só mão não aplaude
Provérbio Árabe