O autor selecionou o Free and Open Source Fund para receber uma doação como parte do programa Write for DOnations.
Mesmo com a crescente popularidade dos serviços em nuvem, a necessidade de executar aplicações nativas ainda existe.
Utilizando o noVNC e o TigerVNC, você pode executar aplicações nativas dentro de um contêiner Docker e acessá-las remotamente usando um navegador Web. Além disso, você pode executar sua aplicação em um servidor com mais recursos de sistema do que você pode ter disponível localmente, o que pode fornecer maior flexibilidade ao se executar grandes aplicações.
Neste tutorial, você irá conteinerizar o Mozilla Thunderbird, um cliente de e-mail usando o Docker. Depois, você irá protegê-lo e fornecer acesso remoto usando o servidor Web Caddy.
Quando você terminar, você será capaz de acessar o Thunderbird a partir de qualquer dispositivo usando apenas um navegador Web. Opcionalmente, você também poderá acessar localmente os arquivos dele usando o WebDAV. Você também terá uma imagem Docker totalmente independente que você pode executar em qualquer lugar.
Antes de iniciar este guia, você precisará do seguinte:
sudo
.supervisord
Agora que seu servidor está em execução e o Docker está instalado, você está pronto para começar a configuração do contêiner da sua aplicação. Como seu contêiner consiste em vários componentes, você precisa usar um gerenciador de processos para iniciá-los e monitorá-los. Aqui, você estará usando o supervisord
. O supervisord
é um gerenciador de processos escrito em Python que é frequentemente usado para orquestrar contêineres complexos.
Primeiro, crie e entre em um diretório chamado thunderbird
para seu contêiner:
- mkdir ~/thunderbird
- cd ~/thunderbird
Agora crie e abra um arquivo chamado supervisord.conf
usando o nano
ou o seu editor preferido:
- nano supervisord.conf
Agora adicione este primeiro bloco de código em supervisord.conf
, que definirá as opções globais para o supervisord:
[supervisord]
nodaemon=true
pidfile=/tmp/supervisord.pid
logfile=/dev/fd/1
logfile_maxbytes=0
Neste bloco, você está configurando o supervisord
propriamente. Você precisa definir o nodaemon
para true
porque ele estará executando dentro de um contêiner Docker como o entrypoint. Portanto, você vai querer que ele permaneça em execução em primeiro plano. Você também está definindo o pidfile
para um caminho acessível por um usuário não-root (mais sobre isso posteriormente), e o logfile
para stdout para que você possa ver os logs.
Em seguida, adicione outro pequeno bloco de código ao supervisord.conf
. Este bloco inicia o TigerVNC, que é um servidor VNC/X11 combinado:
...
[program:x11]
priority=0
command=/usr/bin/Xtigervnc -desktop "Thunderbird" -localhost -rfbport 5900 -SecurityTypes None -AlwaysShared -AcceptKeyEvents -AcceptPointerEvents -AcceptSetDesktopSize -SendCutText -AcceptCutText :0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
Neste bloco, você está configurando o servidor X11. O X11 é um protocolo de servidor de exibição, que é o que permite que as aplicações GUI sejam executadas. Observe que no futuro ele será substituído pelo Wayland, mas o acesso remoto ainda está em desenvolvimento.
Para este contêiner você está usando o TigerVNC e seu servidor VNC embutido. Isso tem uma série de vantagens em relação ao uso de um servidor X11 e VNC separados:
Se você desejar, você pode alterar o argumento para a opção -desktop
a partir do Thunderbird
para outra coisa de sua escolha. O servidor exibirá sua escolha como o título da página Web usada para acessar sua aplicação.
Agora, vamos adicionar um terceiro bloco de código ao supervisord.conf
para iniciar o easy-novnc
:
...
[program:easy-novnc]
priority=0
command=/usr/local/bin/easy-novnc --addr :8080 --host localhost --port 5900 --no-url-password --novnc-params "resize=remote"
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
Neste bloco, você está configurando o easy-novnc
, um servidor standalone que fornece um encapsulamento em torno do noVNC. Este servidor executa dois papéis. Primeiro, ele fornece uma página de conexão simples que lhe permite configurar opções para a conexão e definir as opções padrão. Segundo, ele faz proxy do VNC no WebSocket, que o permite ser acessado através de um navegador Web comum.
Normalmente, o redimensionamento é feito no lado do cliente (ou seja, a escala de imagem), mas você está usando a opção resize=remote
para tirar a vantagem total dos ajustes de resolução remota do TigerVNC. Isso também fornece menor latência em dispositivos mais lentos, como os Chromebooks de entrada:
Nota: Este tutorial usa o easy-novnc
. Se você desejar, você pode usar o websockify
e um servidor Web separado. A vantagem do easy-novnc
é que o uso da memória e o tempo de inicialização são significativamente mais baixos e ele é auto-contido. O easy-novnc
também fornece uma página de conexão mais limpa do que o noVNC padrão e permite definir opções padrão que são úteis para esta configuração (como o resize=remote
).
Agora adicione o seguinte bloco à sua configuração para iniciar o OpenBox, o gerenciador de janelas:
...
[program:openbox]
priority=1
command=/usr/bin/openbox
environment=DISPLAY=:0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
Neste bloco, você está configurando o OpenBox, um gerenciador de janelas X11 leve. Você poderia ignorar este passo, mas sem ele, você não teria barras de título ou seria capaz de redimensionar janelas.
Finalmente, vamos adicionar o último bloco ao supervisord.conf
, que irá iniciar a aplicação principal:
...
[program:app]
priority=1
environment=DISPLAY=:0
command=/usr/bin/thunderbird
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
Neste bloco final, você está definindo priority
para 1
para garantir que o Thunderbird inicie depois do TigerVNC, ou ele encontraria uma condição de corrida e falharia aleatoriamente ao iniciar. Também definimos autorestart=true
para reabrir a aplicação automaticamente se ela fechar erroneamente. A variável de ambiente DISPLAY
diz à aplicação para exibir no servidor VNC que você configurou anteriormente.
Veja como seu supervisord.conf
finalizado vai ficar:
[supervisord]
nodaemon=true
pidfile=/tmp/supervisord.pid
logfile=/dev/fd/1
logfile_maxbytes=0
[program:x11]
priority=0
command=/usr/bin/Xtigervnc -desktop "Thunderbird" -localhost -rfbport 5900 -SecurityTypes None -AlwaysShared -AcceptKeyEvents -AcceptPointerEvents -AcceptSetDesktopSize -SendCutText -AcceptCutText :0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:easy-novnc]
priority=0
command=/usr/local/bin/easy-novnc --addr :8080 --host localhost --port 5900 --no-url-password --novnc-params "resize=remote"
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:openbox]
priority=1
command=/usr/bin/openbox
environment=DISPLAY=:0
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:app]
priority=1
environment=DISPLAY=:0
command=/usr/bin/thunderbird
autorestart=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
Se você quiser conteinerizar uma aplicação diferente, substitua /usr/bin/thunderbird
pelo caminho para o executável da sua aplicação. Caso contrário, você está pronto para configurar o menu principal da sua GUI.
Agora que seu gerenciador de processos está configurado, vamos configurar o menu do OpenBox. Este menu nos permite lançar aplicações dentro do contêiner. Também incluiremos um terminal e monitor de processos para depuração, se necessário.
Dentro do diretório da sua aplicação, use o nano
ou o seu editor de texto favorito para criar e abrir um novo arquivo chamado menu.xml
:
- nano ~/thunderbird/menu.xml
Agora adicione o seguinte código ao menu.xml
:
<?xml version="1.0" encoding="utf-8"?>
<openbox_menu xmlns="http://openbox.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://openbox.org/ file:///usr/share/openbox/menu.xsd">
<menu id="root-menu" label="Openbox 3">
<item label="Thunderbird">
<action name="Execute">
<execute>/usr/bin/thunderbird</execute>
</action>
</item>
<item label="Terminal">
<action name="Execute">
<execute>/usr/bin/x-terminal-emulator</execute>
</action>
</item>
<item label="Htop">
<action name="Execute">
<execute>/usr/bin/x-terminal-emulator -e htop</execute>
</action>
</item>
</menu>
</openbox_menu>
Este arquivo XML contém os itens de menu que aparecerão quando você clicar com o botão direito no desktop. Cada item consiste em uma etiqueta e em uma ação.
Se você quiser conteinerizar uma aplicação diferente, substitua /usr/bin/thunderbird
pelo caminho para o executável da sua aplicação e altere o label
do item.
Agora que o OpenBox está configurado, você criará o Dockerfile, que une tudo que vimos.
Crie um Dockerfile no diretório do seu contêiner:
- nano ~/thunderbird/Dockerfile
Para começar, vamos adicionar código para criar o easy-novnc
:
FROM golang:1.14-buster AS easy-novnc-build
WORKDIR /src
RUN go mod init build && \
go get github.com/geek1011/easy-novnc@v1.1.0 && \
go build -o /bin/easy-novnc github.com/geek1011/easy-novnc
No primeiro estágio, você está compilando o easy-novnc
. Isso é feito em um estágio separado por simplicidade e para salvar espaço - você não precisa da cadeia de ferramentas Go inteira em sua imagem final. Observe o @v1.1.0
no comando de compilação. Isso garante que o resultado seja determinístico, o que é importante porque o Docker faz cache do resultado de cada passo. Se você não tivesse especificado uma versão explícita, o Docker faria referência à versão mais recente do easy-novnc
no momento em que a imagem foi construída pela primeira vez. Além disso, você deseja garantir que você baixe uma versão específica do easy-novnc
, no caso de alterações recentes na interface CLI.
Agora vamos criar o segundo estágio, que se tornará a imagem final. Aqui você estará usando o Debian 10 (buster) como a imagem base. Observe que como isso está em execução em um contêiner, ele funcionará independentemente da distribuição que você está executando em seu servidor.
Em seguida, adicione o seguinte bloco ao seu Dockerfile
:
...
FROM debian:buster
RUN apt-get update -y && \
apt-get install -y --no-install-recommends openbox tigervnc-standalone-server supervisor gosu && \
rm -rf /var/lib/apt/lists && \
mkdir -p /usr/share/desktop-directories
Nesta instrução, você está instalando o Debian 10 como sua imagem base e instalando o mínimo necessário para executar aplicações GUI em um contêiner. Observe que você executa apt-get update
como parte da mesma instrução para evitar problemas de cache do Docker. Para economizar espaço, você também está removendo as listas de pacotes baixadas depois (os pacotes em cache são removidos por padrão). Você também está criando /usr/share/desktop-directories
porque algumas aplicações dependem do diretório existente.
Vamos adicionar outro pequeno bloco de código:
...
RUN apt-get update -y && \
apt-get install -y --no-install-recommends lxterminal nano wget openssh-client rsync ca-certificates xdg-utils htop tar xzip gzip bzip2 zip unzip && \
rm -rf /var/lib/apt/lists
Nesta instrução, você está instalando alguns utilitários e pacotes de utilidade geral. De interesse particular aqui estão o xdg-utils
(que fornece os comandos base usados pelas aplicações desktop no Linux) e os ca-certificates
(que instalam os certificados raiz para nos permitir acessar sites HTTPS).
Agora, podemos adicionar as instruções para a aplicação principal:
...
RUN apt-get update -y && \
apt-get install -y --no-install-recommends thunderbird && \
rm -rf /var/lib/apt/lists
Como antes, aqui estamos instalando a aplicação. Se você estiver conteinerizando uma aplicação diferente, você pode substituir esses comandos por aqueles necessários para instalar seu app específico. Algumas aplicações irão exigir um pouco mais de trabalho para executar dentro do Docker. Por exemplo, se você estiver instalando um app que usa o Chrome, Chromium ou o QtWebEngine, você precisará usar o argumento da linha de comando --no-sandbox
porque ele não será suportado dentro do Docker.
Em seguida, vamos começar a adicionar as instruções para adicionar os últimos arquivos ao contêiner:
...
COPY /bin/easy-novnc /usr/local/bin/
COPY menu.xml /etc/xdg/openbox/
COPY supervisord.conf /etc/
EXPOSE 8080
Aqui você está adicionando os arquivos de configuração que você criou anteriormente à imagem e copiando o binário easy-novnc
do primeiro estágio.
Este próximo bloco de código cria o diretório de dados e adiciona um usuário dedicado para o seu app. Isto é importante porque algumas aplicações se recusam a serem executados como root. Também é uma boa prática não executar aplicações como root, mesmo em um contêiner.
...
RUN groupadd --gid 1000 app && \
useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && \
mkdir -p /data
VOLUME /data
Para garantir um UID/GID
consistente para os arquivos, você está configurando ambos explicitamente para 1000
. Você também está montando um volume no diretório de dados para garantir que ele persista entre reinicializações.
Finalmente, vamos adicionar as instruções de lançamento.
...
CMD ["sh", "-c", "chown app:app /data /dev/stdout && exec gosu app supervisord"]
Ao definir o comando padrão para supervisord
, o gerenciador irá lançar os processos necessários para executar sua aplicação. Neste caso, você está usando CMD
em vez de ENTRYPOINT
. Na maioria dos casos, isso não faria diferença, mas usar o CMD
é mais adequado para este propósito por algumas razões. Primeiro, o supervisord
não aceita quaisquer argumentos relevantes para nós, e se você fornecer argumentos ao contêiner eles substituem o CMD
e são anexados ao ENTRYPOINT
. Segundo, usar o CMD
nos permite fornecer um comando inteiramente diferente (que será executado por /bin/sh -c
) ao passar argumentos para o contêiner, o que torna a depuração mais fácil.
E, finalmente, você precisa executar o chown
como root antes de iniciar o supervisord
para evitar problemas de permissão no volume de dados e para permitir que os processos filhos abram o stdout
. Isso também significa que você precisa usar gosu
em vez da instrução USER
para trocar o usuário.
Aqui está como o seu Dockerfile
finalizado vai parecer:
FROM golang:1.14-buster AS easy-novnc-build
WORKDIR /src
RUN go mod init build && \
go get github.com/geek1011/easy-novnc@v1.1.0 && \
go build -o /bin/easy-novnc github.com/geek1011/easy-novnc
FROM debian:buster
RUN apt-get update -y && \
apt-get install -y --no-install-recommends openbox tigervnc-standalone-server supervisor gosu && \
rm -rf /var/lib/apt/lists && \
mkdir -p /usr/share/desktop-directories
RUN apt-get update -y && \
apt-get install -y --no-install-recommends lxterminal nano wget openssh-client rsync ca-certificates xdg-utils htop tar xzip gzip bzip2 zip unzip && \
rm -rf /var/lib/apt/lists
RUN apt-get update -y && \
apt-get install -y --no-install-recommends thunderbird && \
rm -rf /var/lib/apt/lists
COPY /bin/easy-novnc /usr/local/bin/
COPY menu.xml /etc/xdg/openbox/
COPY supervisord.conf /etc/
EXPOSE 8080
RUN groupadd --gid 1000 app && \
useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && \
mkdir -p /data
VOLUME /data
CMD ["sh", "-c", "chown app:app /data /dev/stdout && exec gosu app supervisord"]
Salve e feche seu Dockerfile
. Agora estamos prontos para compilar e executar nosso contêiner e, em seguida, acessar o Thunderbird - uma aplicação GUI.
O próximo passo é compilar seu contêiner e configurá-lo para executar na inicialização. Você também irá configurar um volume para preservar os dados da aplicação entre reinicializações e atualizações.
Primeiro compile seu contêiner. Certifique-se de executar esses comandos no diretório ~/thunderbird
:
- docker build -t thunderbird .
Agora crie uma nova rede que será compartilhada entre os contêineres da aplicação:
- docker network create thunderbird-net
Em seguida, crie um volume para armazenar os dados da aplicação:
- docker volume create thunderbird-data
Finalmente, execute-a e defina-a para reinicializar automaticamente:
- docker run --detach --restart=always --volume=thunderbird-data:/data --net=thunderbird-net --name=thunderbird-app thunderbird
Observe que se você quiser, você pode substituir o thunderbird-app
após a opção --name
por um nome diferente. Seja o que for que você escolheu, sua aplicação está agora conteinerizada e em execução. Agora vamos usar o servidor Web Caddy para protegê-la e conectar-se remotamente a ela.
Neste passo, você irá configurar o servidor Web Caddy para fornecer autenticação e, opcionalmente, acesso remoto a arquivos através do WebDAV. Por simplicidade, e para lhe permitir utilizá-lo com seu proxy reverso existente, você irá executá-lo em um outro contêiner
Crie um novo diretório e então mova-se para ele:
- mkdir ~/caddy
- cd ~/caddy
Agora crie um novo Dockerfile
usando o nano
ou o seu editor preferido:
- nano ~/caddy/Dockerfile
Então, adicione as seguintes diretivas:
FROM golang:1.14-buster AS caddy-build
WORKDIR /src
RUN echo 'module caddy' > go.mod && \
echo 'require github.com/caddyserver/caddy/v2 v2.0.0' >> go.mod && \
echo 'require github.com/mholt/caddy-webdav v0.0.0-20200523051447-bc5d19941ac3' >> go.mod
RUN echo 'package main' > caddy.go && \
echo 'import caddycmd "github.com/caddyserver/caddy/v2/cmd"' >> caddy.go && \
echo 'import _ "github.com/caddyserver/caddy/v2/modules/standard"' >> caddy.go && \
echo 'import _ "github.com/mholt/caddy-webdav"' >> caddy.go && \
echo 'func main() { caddycmd.Main() }' >> caddy.go
RUN go build -o /bin/caddy .
FROM debian:buster
RUN apt-get update -y && \
apt-get install -y --no-install-recommends gosu && \
rm -rf /var/lib/apt/lists
COPY /bin/caddy /usr/local/bin/
COPY Caddyfile /etc/
EXPOSE 8080
RUN groupadd --gid 1000 app && \
useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && \
mkdir -p /data
VOLUME /data
WORKDIR /data
CMD ["sh", "-c", "chown app:app /data && exec gosu app /usr/local/bin/caddy run -adapter caddyfile -config /etc/Caddyfile"]
Este Dockerfile compila o Caddy com o plug-in WebDAV habilitado e, em seguida, o lança na porta 8080
com o Caddyfile
em /etc/Caddyfile
. Salve e feche o arquivo.
Em seguida, você irá configurar o servidor Web Caddy. Crie um arquivo chamado Caddyfile
no diretório que você acabou de criar:
- nano ~/caddy/Caddyfile
Agora adicione o seguinte bloco de código ao seu Caddyfile
:
{
order webdav last
}
:8080 {
log
root * /data
reverse_proxy thunderbird-app:8080
handle /files/* {
uri strip_prefix /files
file_server browse
}
redir /files /files/
handle /webdav/* {
uri strip_prefix /webdav
webdav
}
redir /webdav /webdav/
basicauth /* {
{env.APP_USERNAME} {env.APP_PASSWORD_HASH}
}
}
Este Caddyfile
faz proxy do diretório raiz para o contêiner thunderbird-app
que você criou no Passo 4 (o Docker o resolve para o IP correto). Ele também irá fornecer um navegador de arquivos baseado em web somente leitura em /files
e executar um servidor WebDAV em /webdav
, que você pode montar localmente para acessar seus arquivos. O nome de usuário e a senha são lidos a partir das variáveis de ambiente APP_USERNAME
e APP_PASSWORD_HASH
.
Agora compile o contêiner:
- docker build -t thunderbird-caddy .
O Caddy v.2 requer que você faça um hash da sua senha desejada. Execute o seguinte comando e lembre-se de substituir mypass
por uma senha forte da sua escolha:
- docker run --rm -it thunderbird-caddy caddy hash-password -plaintext 'mypass'
Este comando irá exibir uma string de caracteres. Copie isso para sua área de transferência em preparação para executar o próximo comando.
Agora tudo está pronto para executar o contêiner. Certifique-se de substituir myuser
por um nome de usuário da sua escolha e substitua mypass-hash
pela saída do comando que você executou no passo anterior. Você também pode alterar a porta (8080
aqui) para acessar seu servidor em uma porta diferente:
- docker run --detach --restart=always --volume=thunderbird-data:/data --net=thunderbird-net --name=thunderbird-web --env=APP_USERNAME="myuser" --env=APP_PASSWORD_HASH="mypass-hash" --publish=8080:8080 thunderbird-caddy
Agora estamos prontos para acessar e testar nossa aplicação.
Vamos acessar sua aplicação e garantir que ela está funcionando.
Primeiro, abra http://your_server_ip:8080
em um navegador Web, faça login com as credenciais que você escolheu anteriormente, e clique em Connect.
Agora você deve ser capaz de interagir com a aplicação, e ela deve redimensionar automaticamente para se encaixar na janela do seu navegador.
Se você clicar com o botão direito no desktop preto, você deve ver um menu que lhe permite acessar um terminal. Se você clicar com o botão do meio, verá uma lista de janelas.
Agora abra http://your_server_ip:8080/files/
em um navegador. Você deve ser capaz de acessar seus arquivos.
Opcionalmente, você pode tentar montar http://your_server_ip:8080/webdav/
em um cliente WebDAV. Você deve ser capaz de acessar e modificar seus arquivos diretamente. Se você usar a opção Mapear unidade de rede no Windows Explorer, você precisará usar um proxy reverso para adicionar HTTPS ou definir HKLM\SYSTEM\CurrentControlSet\Services\WebClient\Parameters\BasicAuthLevel
para DWORD:2
.
Em ambos os casos, sua aplicação GUI nativa está pronta para uso remoto.
Agora você configurou com êxito um contêiner Docker para o Thunderbird e, em seguida, usando o Caddy, você configurou o acesso a ele através de um navegador Web. Se você precisar atualizar sua aplicação, pare os contêineres, execute docker rm thunderbird-app thunderbird-web
, recompile as imagens e, em seguida, execute novamente os comandos docker run
das etapas anteriores acima. Seus dados ainda serão preservados uma vez que eles são armazenados em um volume.
Se você quiser aprender mais sobre comandos básicos do Docker, você pode ler este tutorial ou esse guia de consulta rápida. Para uso a longo prazo, convém habilitar o HTTPS (isso requer um domínio) para segurança adicional.
Além disso, se você estiver implantando mais de uma aplicação, você pode querer usar o Docker Compose ou o Kubernetes em vez de iniciar cada contêiner manualmente. E lembre-se, este tutorial pode servir como base para executar qualquer outra aplicação Linux em seu servidor, incluindo:
Esta última opção demonstra o grande potencial de conteinerizar e acessar remotamente aplicações GUI. Com esta configuração, agora você usa um servidor com mais poder de computação do que localmente. Com isso, você executa ferramentas com consumo intensivo de recursos como o Cutter.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!