diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000..cefa5bbdc9
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+.git
+.DS_Store
+.vscode
+stage
+include/ost-config.php
+include/mpdf/tmp
+include/mpdf/ttfontdata
diff --git a/.env.docker.example b/.env.docker.example
new file mode 100644
index 0000000000..e32e76217d
--- /dev/null
+++ b/.env.docker.example
@@ -0,0 +1,8 @@
+OSTICKET_HTTP_PORT=8080
+OSTICKET_DEPLOY_ON_START=1
+OSTICKET_DEFAULT_LANG=pt_BR
+MYSQL_DATABASE=osticket
+MYSQL_USER=osticket
+MYSQL_PASSWORD=osticket
+MYSQL_ROOT_PASSWORD=root
+TZ=America/Sao_Paulo
diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml
new file mode 100644
index 0000000000..e65947e4c3
--- /dev/null
+++ b/.github/workflows/publish-image.yml
@@ -0,0 +1,62 @@
+name: Publish Docker Image
+
+on:
+ push:
+ branches:
+ - '**'
+ tags:
+ - 'v*'
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: publish-image-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ packages: write
+
+jobs:
+ docker:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to GHCR
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract Docker metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ghcr.io/${{ github.repository }}
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=sha,prefix=sha-
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and publish image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: ./Dockerfile
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.event_name != 'pull_request' }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
diff --git a/.gitignore b/.gitignore
index 7769425590..736a06a421 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,4 @@ stage
include/mpdf/ttfontdata
include/mpdf/tmp
nbproject/
+.env.docker
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000..cffd55edac
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,52 @@
+FROM php:8.3-apache-bookworm
+
+ENV APACHE_DOCUMENT_ROOT=/var/www/html \
+ OSTICKET_APP=/var/www/html \
+ OSTICKET_SRC=/srv/osticket-src
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ git \
+ libc-client2007e-dev \
+ libfreetype6-dev \
+ libicu-dev \
+ libjpeg62-turbo-dev \
+ libkrb5-dev \
+ libpng-dev \
+ libxml2-dev \
+ libzip-dev \
+ && docker-php-source extract \
+ && docker-php-ext-configure gd --with-freetype --with-jpeg \
+ && ac_cv_utf8_mime2text=new ac_cv_u8t_decompose=yes docker-php-ext-configure imap --with-kerberos --with-imap-ssl \
+ && docker-php-ext-install -j"$(nproc)" \
+ gd \
+ gettext \
+ imap \
+ intl \
+ mysqli \
+ opcache \
+ zip \
+ && pecl install apcu \
+ && docker-php-ext-enable apcu \
+ && docker-php-source delete \
+ && a2enmod expires headers rewrite \
+ && sed -ri "s!/var/www/html!${APACHE_DOCUMENT_ROOT}!g" \
+ /etc/apache2/apache2.conf \
+ /etc/apache2/conf-available/*.conf \
+ /etc/apache2/sites-available/*.conf \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY . /srv/osticket-src
+COPY docker/i18n/ /opt/osticket-i18n/
+COPY docker/apache/osticket.conf /etc/apache2/conf-available/osticket.conf
+COPY docker/php/osticket.ini /usr/local/etc/php/conf.d/osticket.ini
+COPY docker/entrypoint.sh /usr/local/bin/osticket-entrypoint
+
+RUN chmod +x /usr/local/bin/osticket-entrypoint \
+ && a2enconf osticket \
+ && mkdir -p /var/www/html
+
+WORKDIR /var/www/html
+
+ENTRYPOINT ["osticket-entrypoint"]
+CMD ["apache2-foreground"]
diff --git a/README.docker.md b/README.docker.md
new file mode 100644
index 0000000000..45f69c8887
--- /dev/null
+++ b/README.docker.md
@@ -0,0 +1,54 @@
+# osTicket com Docker
+
+Este repositório agora inclui um ambiente local com `docker compose` para subir o osTicket com `PHP 8.4 + Apache` e `MariaDB 10.11`.
+
+## Como subir
+
+1. Copie o arquivo de exemplo:
+
+ ```bash
+ cp .env.docker.example .env.docker
+ ```
+
+2. Suba os containers:
+
+ ```bash
+ docker compose --env-file .env.docker up --build
+ ```
+
+3. Abra:
+
+ ```text
+ http://localhost:8080/setup/
+ ```
+
+## Banco para o instalador
+
+Use os valores abaixo no instalador web, a menos que tenha alterado o `.env.docker`:
+
+- Host: `db`
+- Database: `osticket`
+- User: `osticket`
+- Password: `osticket`
+
+## Como funciona
+
+- O container `web` instala as extensoes PHP exigidas pelo projeto, incluindo `imap`.
+- O container tambem habilita `APCu` para melhorar cache e desempenho.
+- No startup, o entrypoint executa `php manage.php deploy --setup /var/www/html`.
+- O codigo-fonte local e montado em `/srv/osticket-src` apenas para servir de origem ao `deploy`.
+- O deploy final fica persistido no volume `osticket-app`.
+- O banco fica persistido no volume `osticket-db`.
+
+## Atualizando o codigo
+
+Por padrao, o container reaplica o `deploy` a cada start com `OSTICKET_DEPLOY_ON_START=1`. Se quiser desligar esse comportamento, ajuste a variavel no `.env.docker`.
+
+## Pos-instalacao
+
+Depois que a instalacao terminar, vale remover a pasta `setup` do volume implantado e travar o arquivo de configuracao:
+
+```bash
+docker compose exec web rm -rf /var/www/html/setup
+docker compose exec web chmod 0644 /var/www/html/include/ost-config.php
+```
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000..6a716648dc
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,49 @@
+services:
+ web:
+ build:
+ context: .
+ depends_on:
+ db:
+ condition: service_healthy
+ environment:
+ OSTICKET_APP: /var/www/html
+ OSTICKET_DB_HOST: ${OSTICKET_DB_HOST:-db}
+ OSTICKET_DB_NAME: ${MYSQL_DATABASE:-osticket}
+ OSTICKET_DB_PASS: ${MYSQL_PASSWORD:-osticket}
+ OSTICKET_DB_PREFIX: ${OSTICKET_DB_PREFIX:-ost_}
+ OSTICKET_DB_USER: ${MYSQL_USER:-osticket}
+ OSTICKET_DEFAULT_LANG: ${OSTICKET_DEFAULT_LANG:-pt_BR}
+ OSTICKET_DEPLOY_ON_START: ${OSTICKET_DEPLOY_ON_START:-1}
+ OSTICKET_SRC: /srv/osticket-src
+ TZ: ${TZ:-America/Sao_Paulo}
+ ports:
+ - "${OSTICKET_HTTP_PORT:-8080}:80"
+ volumes:
+ - .:/srv/osticket-src:ro
+ - osticket-app:/var/www/html
+ restart: unless-stopped
+
+ db:
+ image: mariadb:10.11
+ command:
+ - --character-set-server=utf8mb4
+ - --collation-server=utf8mb4_unicode_ci
+ environment:
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-osticket}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-osticket}
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
+ MYSQL_USER: ${MYSQL_USER:-osticket}
+ TZ: ${TZ:-America/Sao_Paulo}
+ healthcheck:
+ test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
+ interval: 10s
+ timeout: 5s
+ retries: 10
+ start_period: 20s
+ restart: unless-stopped
+ volumes:
+ - osticket-db:/var/lib/mysql
+
+volumes:
+ osticket-app:
+ osticket-db:
diff --git a/docker/apache/osticket.conf b/docker/apache/osticket.conf
new file mode 100644
index 0000000000..395006b7f0
--- /dev/null
+++ b/docker/apache/osticket.conf
@@ -0,0 +1,5 @@
+