Provisionando um simples repositório Git com Terraform
Utilizei da ferramenta Terraform para provisionar em uma EC2 t3.small um repositório git para enviar meus projetos.
Publicado em: · Última atualização: · 8 min de leitura
O porquê deste projeto #
Estabelecer de forma prática e versionável um respositório remoto próprio para enviar meus projetos e coisas que tais. Utilizei das ferramentas Git como versionamento, Ngix como servidor e Terraform como infraestrutura-como-código, configurando assim o ambiente em uma instância AWS EC2 t3.small de 2GB, sem necessidade de armazenamento extra.
Objetivos do projeto #
- Provisionar infraestrutura na AWS de forma declarativa com Terraform;
- Criar uma instância EC2 Ubuntu para hospedar um servidor Git;
- Permitir operações Git apenas via SSH (clone, push, pull);
- Disponibilizar uma interface web de visualização dos repositórios com GitWeb;
- Expor o GitWeb exclusivamente via CloudFront (HTTPS);
- Restringir o acesso HTTP da instância apenas ao CloudFront;
- Manter o ambiente simples, sem ALB, WAF ou serviços adicionais.
Visão geral da arquitetura #
A arquitetura final é composta por poucos elementos, mas bem definidos:
EC2 (Ubuntu 22.04)
Responsável por:- Git (repositórios bare)
- GitWeb
- Nginx
- fcgiwrap
CloudFront Atua como camada de exposição segura:
- HTTPS para o público
- Origem restrita à instância EC2
- Nenhum acesso HTTP direto à instância
Rede (VPC padrão)
- Sub-rede pública
- Internet Gateway
- Tabela de rotas explícita
Repositório Git em produção #

Link do repositório em produção –> aqui
Provisionamento com Terraform #
Pensando em deixar o mais simples possível, decidi por escolher uma configuração simples para provisionar o ambiente na AWS, sem muitas atribuições ou configurações que não sejam úteis para um simples repositório Git.
Foram provisionados:
- VPC padrão da AWS;
- Sub-rede pública em uma AZ;
- Internet Gateway e associação explícita na tabela de rotas;
- Security Group com regras mínimas:
- SSH (22): permitido apenas a partir do meu IP público;
- HTTP (80): permitido somente para o prefix list gerenciado da AWS referente ao CloudFront origin-facing;
- Instância EC2 t3.small, com Ubuntu 22.04;
- Script
user_data_config.shpara deploy automático das configurações do servidor para o repositório git;
- Script
- Elastic IP para garantir estabilidade da origem;
- Distribuição CloudFront apontando para o IP público da instância.
variables.tf:
variable "region" {
type = string
default = "us-east-1"
}
variable "my_ip_cidr" {
type = string
description = "Your public IP in CIDR format, e.g. 203.0.113.10/32"
}
variable "key_name" {
type = string
description = "Existing EC2 Key Pair name in the target region"
}
variable "instance_type" {
type = string
default = "t3.small"
}
variable "project_name" {
type = string
default = "host-gitweb"
}
main.tf:
provider "aws" {
region = var.region
}
# Default VPC
data "aws_vpc" "default" {
default = true
}
# Default subnet in us-east-1a
resource "aws_default_subnet" "a" {
availability_zone = "us-east-1a"
}
# Internet Gateway for default VPC
resource "aws_internet_gateway" "igw" {
vpc_id = data.aws_vpc.default.id
}
# Public route table (0.0.0.0/0 -> IGW)
resource "aws_route_table" "public" {
vpc_id = data.aws_vpc.default.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "${var.project_name}-rt-public"
}
}
# Associate route table to subnet
resource "aws_route_table_association" "a" {
subnet_id = aws_default_subnet.a.id
route_table_id = aws_route_table.public.id
}
# Ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# CloudFront origin-facing managed prefix list
data "aws_ec2_managed_prefix_list" "cloudfront_origin" {
name = "com.amazonaws.global.cloudfront.origin-facing"
}
# Security Group
resource "aws_security_group" "gitweb" {
name = "${var.project_name}-sg"
description = "SSH from my IP; HTTP only from CloudFront origin-facing"
vpc_id = data.aws_vpc.default.id
ingress {
description = "SSH only from my IP"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.my_ip_cidr]
}
ingress {
description = "HTTP only from CloudFront origin-facing"
from_port = 80
to_port = 80
protocol = "tcp"
prefix_list_ids = [data.aws_ec2_managed_prefix_list.cloudfront_origin.id]
}
egress {
description = "Allow outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# EC2
resource "aws_instance" "gitweb" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = aws_default_subnet.a.id
vpc_security_group_ids = [aws_security_group.gitweb.id]
key_name = var.key_name
# Launch the script into the instance
user_data = file("${path.module}/user_data_config.sh")
tags = {
Name = "${var.project_name}-ec2"
}
}
# Elastic IP
resource "aws_eip" "gitweb" {
domain = "vpc"
instance = aws_instance.gitweb.id
tags = {
Name = "${var.project_name}-eip"
}
}
# CloudFront distribution
resource "aws_cloudfront_distribution" "gitweb" {
enabled = true
is_ipv6_enabled = true
comment = "GitWeb behind CloudFront (origin restricted)"
origin {
domain_name = aws_eip.gitweb.public_dns
origin_id = "${var.project_name}-origin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
target_origin_id = "${var.project_name}-origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
forwarded_values {
query_string = true
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 0
max_ttl = 60
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
O restante das outras configurações podem ser encontradas aqui.
Seguindo com a aplicação da infraestrutura:
$ terraform apply


Tive que destruir algumas vezes a infraestrutura para realizar alguns testes, então alguns prints estarão com um IP elástico fixo distinto.
Com a infraestrutura já estabelecida, partamos para a função do script user_data_config.sh.
Configuração do servidor via user_data_config.sh
#
O script em questão:
#!/bin/bash
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y git gitweb fcgiwrap nginx
# --- a dedicated 'git' user, git-shell only ---
if ! id git >/dev/null 2>&1; then
useradd -m -d /home/git -s /usr/bin/git-shell git
fi
# Repo root /var/lib/git
mkdir -p /var/lib/git
chown -R git:git /var/lib/git
chmod 2750 /var/lib/git
# --- GitWeb config ---
cat >/etc/gitweb.conf <<'EOF'
$projectroot = "/var/lib/git";
$projects_list = $projectroot;
$site_name = "My Git Server (GitWeb)";
EOF
systemctl enable --now fcgiwrap
# --- Nginx serving GitWeb via fcgiwrap ---
cat >/etc/nginx/sites-available/gitweb <<'EOF'
server {
listen 80;
server_name _;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy no-referrer always;
location = / { return 302 /cgi-bin/gitweb.cgi; }
location /gitweb/static/ {
alias /usr/share/gitweb/static/;
}
location /cgi-bin/gitweb.cgi {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/gitweb.cgi;
fastcgi_param GITWEB_CONFIG /etc/gitweb.conf;
fastcgi_pass unix:/run/fcgiwrap.socket;
}
}
EOF
rm -f /etc/nginx/sites-enabled/default
ln -sf /etc/nginx/sites-available/gitweb /etc/nginx/sites-enabled/gitweb
nginx -t
systemctl enable --now nginx
# --- SSH hardening ---
sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
# Your requirement: allow root login (key-only)
sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config
systemctl restart ssh
# Convenience: allow same key used for ubuntu user to be used for git user
if [ -f /home/ubuntu/.ssh/authorized_keys ]; then
install -d -m 700 -o git -g git /home/git/.ssh
cat /home/ubuntu/.ssh/authorized_keys > /home/git/.ssh/authorized_keys
chown git:git /home/git/.ssh/authorized_keys
chmod 600 /home/git/.ssh/authorized_keys
fi
Com a infraestrutura provisionada, o próximo passo foi garantir que a instância EC2 subisse já configurada para atuar como servidor Git.
Para isso, utilizei um script user_data_config.sh, executado automaticamente no primeiro boot da instância.
A ideia foi simples: subir o servidor já pronto, sem necessita de configuraçõs manuais de boot posteriores.

Instalação dos pacotes essenciais #
Logo no início do script, atualizei os repositórios e instalei apenas os pacotes necessários:
git: para gerenciamento dos repositórios;gitweb: interface web de visualização;nginx: servidor HTTP;fcgiwrap: para permitir que o Nginx execute o CGI do GitWeb.
Nada além do necessário, mantendo o ambiente leve e fácil de entender.
Criação do usuário git
#
Seguindo a prática comum para servidores Git, criei um usuário dedicado chamado git.
Esse usuário:
- não possui shell interativo;
- utiliza exclusivamente o
git-shell; - é responsável por hospedar e gerenciar os repositórios.
Isso garante uma separação clara entre:
- administração do sistema (ubuntu/root);
- operações Git (git).
Estrutura dos repositórios #
Defini o diretório /var/lib/git como raiz dos repositórios.
Esse caminho:
- é simples;
- é facilmente referenciável pelo GitWeb;
- facilita permissões e organização.
Todos os repositórios criados são do tipo bare, conforme esperado para servidores Git.
Configurando os respositórios remoto e local #
Para iniciar o repositório git “bare” (vazio), tive que executar os seguintes comandos no servidor, para configurar o ambiente remoto:
$ sudo -u git git init --bare /var/lib/git/<projeto>.git

Configuração do GitWeb #
Em seguida, configurei o arquivo /etc/gitweb.conf apontando o GitWeb para o diretório correto dos repositórios.
A ideia foi permitir que o GitWeb:
- detecte automaticamente os repositórios;
- liste todos os projetos disponíveis;
- não dependa de arquivos extras ou indexações manuais.
Essa configuração foi propositalmente simples para evitar erros comuns e facilitar manutenção.
Integração GitWeb + Nginx #
O GitWeb funciona como um CGI.
Para integrá-lo ao Nginx, utilizei o fcgiwrap.
Configurei o Nginx para:
- responder na porta 80;
- redirecionar o arquivo
/para o GitWeb; - servir corretamente os arquivos estáticos do GitWeb;
- executar o
gitweb.cgivia FastCGI.
Essa abordagem elimina a necessidade de Apache e mantém o stack mais simples.
Hardening básico de SSH #
Como o acesso ao servidor é feito via SSH, desativei autenticação por senha, mantendo apenas key-based authentication.
Também habilitei login como root via chave, por necessidade de testes e administração direta, ciente de que isso não é o padrão mais restritivo, mas aceitável dentro do contexto do projeto.
Preparação de acesso Git via SSH #
Por fim, garanti que a chave SSH associada à instância fosse automaticamente instalada para o usuário git.
Isso resolve um ponto importante:
- o acesso SSH administrativo funciona com
ubuntu; - o
git pushfunciona com o usuáriogit; - ambos utilizam a mesma chave, sem configuração manual adicional.
Com isso, qualquer git push via SSH passa a funcionar imediatamente após o provisionamento.
Configuração específica no ~/.ssh/config
#
Para facilitar a utilização do git junto ao servidor, como também para separar logicamente minhha conexão a este ou outros repositórios git remotos, configurei no arquivo ~/.ssh/config as especificações de conexão ao repositório remoto aqui discutido:

Então, fiz as seguintes modificações no repositório local:

Adicionei o origin apontando para o repositório remoto, que recebe/envia pull/push via ssh, que são enviados pela chave do usuário git no servidor.
Todo o controle de escrita acontece exclusivamente via SSH, utilizando o usuário git.
Resultado final #
Ao final do boot da instância:
- o servidor já aceita conexões SSH;
- repositórios Git podem ser criados imediatamente;
- pushes via
git pushfuncionam sem ajustes adicionais; - o GitWeb já está disponível e funcional;
- a interface web é exposta de forma segura via CloudFront.
- É possível adicionar outras funcionalidades na infraestrutura posteriormente, como ALB e Backup automático;
- Também é possível configurar uma outra interface Git posterior, caso surja necessidade.
Todo o ambiente nasce pronto para uso, reproduzível e controlado via Terraform.
Este repositório está disponibilizado aqui, igualmente.