# How to Deploy TaxHacker to Production: Complete Docker Self-Hosting Guide

> Deploy TaxHacker to production with Docker Compose. This guide covers setting up Next.js and PostgreSQL for self-hosting your TaxHacker instance efficiently.

- Repository: [Vasily Zubarev/TaxHacker](https://github.com/vas3k/TaxHacker)
- Tags: how-to-guide
- Published: 2026-04-01

---

**Deploy TaxHacker to production using Docker Compose with two containers—a Next.js 15+ application and PostgreSQL 17—configured via environment variables parsed in [`lib/config.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/config.ts) and exposed on port 7331.**

TaxHacker is a self-hosted, AI-powered accounting application maintained in the **vas3k/TaxHacker** repository. When you deploy TaxHacker to production, you run a containerized stack orchestrated by Docker, with all sensitive configuration centralized and validated through a Zod schema in [`lib/config.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/config.ts).

## Production Architecture Overview

TaxHacker runs as a multi-container system designed for reproducible deployments. The architecture consists of a Next.js application server and a PostgreSQL database, connected via Prisma ORM.

### The Application Stack

The **`app`** container runs the Next.js 15+ server built from the multi-stage `Dockerfile`. The build process uses `node:23-slim` as a base, installs `openssl` (required for Prisma), and bundles runtime dependencies including `ghostscript` and `graphicsmagick` for PDF and image processing. The production stage copies only built artifacts and runs [`docker-entrypoint.sh`](https://github.com/vas3k/TaxHacker/blob/main/docker-entrypoint.sh), which executes database migrations before starting the server on port **7331**.

The **`postgres`** container runs PostgreSQL 17 using the official image, persisting data to a Docker volume. You can replace this with an external database by modifying the `DATABASE_URL` environment variable.

### Configuration Management

All production settings derive from environment variables defined in `.env.example` and strictly parsed by the Zod schema in [`lib/config.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/config.ts) (lines 3–23). This file exports a type-safe `config` object used throughout the application for AI provider keys (`OPENAI_API_KEY`, `GOOGLE_API_KEY`, `MISTRAL_API_KEY`), authentication secrets (`BETTER_AUTH_SECRET`), and infrastructure paths (`UPLOAD_PATH`).

## Deployment Methods

You can deploy TaxHacker using three approaches depending on your infrastructure requirements: quick start with pre-built images, building from source, or a hardened production configuration.

### Method 1: Quick Start with Docker Compose

The fastest way to deploy TaxHacker to production uses the provided [`docker-compose.yml`](https://github.com/vas3k/TaxHacker/blob/main/docker-compose.yml) file, which pulls the pre-built image from GitHub Container Registry.

```bash

# Download the compose configuration

curl -O https://raw.githubusercontent.com/vas3k/TaxHacker/main/docker-compose.yml

# Create environment variables

cat > .env <<EOF
SELF_HOSTED_MODE=true
UPLOAD_PATH=./data/uploads
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/taxhacker
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
OPENAI_API_KEY=sk-your-key-here
EOF

# Deploy the stack

docker compose up -d

```

This command starts both containers, binds the application to port **7331**, and automatically initializes the database using the entrypoint script at [`docker-entrypoint.sh`](https://github.com/vas3k/TaxHacker/blob/main/docker-entrypoint.sh).

### Method 2: Building from Source

For custom modifications or air-gapped environments, build the image locally from the `Dockerfile`:

```bash
git clone https://github.com/vas3k/TaxHacker.git
cd TaxHacker

docker build -t taxhacker:custom .

docker run -d \
  -p 7331:7331 \
  -e SELF_HOSTED_MODE=true \
  -e DATABASE_URL=postgresql://user:pass@host:5432/taxhacker \
  -e BETTER_AUTH_SECRET=$(openssl rand -base64 32) \
  --name taxhacker_app taxhacker:custom

```

The Dockerfile implements a multi-stage build that minimizes the final image size by separating the build environment (requiring `openssl` and dev dependencies) from the production runtime.

### Method 3: Production-Grade Deployment

For public-facing deployments behind a reverse proxy, use [`docker-compose.production.yml`](https://github.com/vas3k/TaxHacker/blob/main/docker-compose.production.yml):

```bash
curl -O https://raw.githubusercontent.com/vas3k/TaxHacker/main/docker-compose.production.yml

# Edit .env to set SELF_HOSTED_MODE=false and BASE_URL=https://your-domain.com

docker compose -f docker-compose.production.yml up -d

```

Key differences from the quick-start method:
- `SELF_HOSTED_MODE` is set to `false`, disabling the auto-login shortcut for security
- The container binds to `127.0.0.1:7331` only, requiring an external reverse proxy (Nginx, Traefik, or Caddy) for TLS termination
- External environment files are strictly enforced via `env_file:` declarations

## Database Initialization and Migrations

The Prisma ORM handles schema management through [`lib/db.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/db.ts), which exports a singleton client with connection pooling. Migrations run automatically when the container starts via [`docker-entrypoint.sh`](https://github.com/vas3k/TaxHacker/blob/main/docker-entrypoint.sh), which executes:

```bash
npx prisma migrate deploy

```

To manually trigger a migration in a running container:

```bash
docker exec -it taxhacker_app npx prisma migrate deploy

```

The schema definition in `prisma/schema.prisma` defines tables for users, transactions, uploaded files, and application settings, which are created automatically on first run.

## Required Environment Configuration

Before deploying TaxHacker to production, populate your `.env` file based on `.env.example` with these critical variables:

- **`BETTER_AUTH_SECRET`**: A random string of at least 16 characters for password-less authentication encryption
- **`DATABASE_URL`**: PostgreSQL connection string (e.g., `postgresql://postgres:postgres@postgres:5432/taxhacker`)
- **`UPLOAD_PATH`**: Absolute path for persistent file storage (e.g., `/app/data/uploads`)
- **`OPENAI_API_KEY`**, **`GOOGLE_API_KEY`**, or **`MISTRAL_API_KEY`**: Required only if using AI-powered document extraction
- **`RESEND_API_KEY`** and **`RESEND_FROM_EMAIL`**: Required for transactional email delivery in production
- **`STRIPE_SECRET_KEY`** and **`STRIPE_WEBHOOK_SECRET`**: Required only if operating as a paid SaaS

These values are validated at runtime by the Zod schema in [`lib/config.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/config.ts), which throws explicit errors if required variables are missing or malformed.

## Security and Operations Checklist

Complete these steps before exposing TaxHacker to public traffic:

1. **Generate a secure `BETTER_AUTH_SECRET`** using `openssl rand -base64 32`
2. **Configure TLS termination** at your reverse proxy; the container serves plain HTTP on port 7331
3. **Back up the PostgreSQL volume** regularly (the `pgdata` volume in Docker Compose)
4. **Set appropriate AI rate limits** in [`next.config.ts`](https://github.com/vas3k/TaxHacker/blob/main/next.config.ts) if exposing to multiple users
5. **Verify file upload permissions** on the `UPLOAD_PATH` directory to prevent unauthorized access

## Summary

- **Deploy TaxHacker to production** using Docker Compose with the official `ghcr.io/vas3k/taxhacker:latest` image or a custom build from the `Dockerfile`
- **Run two containers**: a Next.js 15+ `app` container and a PostgreSQL 17 `postgres` container
- **Configure via [`lib/config.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/config.ts)**: Validate all settings through environment variables using the `.env.example` template
- **Expose port 7331**: Bind to localhost for production deployments behind a reverse proxy, or directly for private networks
- **Initialize automatically**: Database migrations run via [`docker-entrypoint.sh`](https://github.com/vas3k/TaxHacker/blob/main/docker-entrypoint.sh) on container startup
- **Enable AI features**: Provide OpenAI, Gemini, or Mistral API keys for automatic document parsing

## Frequently Asked Questions

### What ports and protocols does TaxHacker require?

TaxHacker exposes **port 7331** over HTTP. You must place it behind a reverse proxy such as Nginx or Traefik to handle TLS/HTTPS termination in production. PostgreSQL communicates over port 5432 internally within the Docker network.

### Can I use an external database instead of the Docker PostgreSQL container?

Yes. Set `DATABASE_URL` to point to your external PostgreSQL 17 instance (e.g., AWS RDS or Supabase) and remove the `postgres` service from your [`docker-compose.yml`](https://github.com/vas3k/TaxHacker/blob/main/docker-compose.yml). Ensure the database is accessible from the TaxHacker container and that credentials match the format `postgresql://user:password@host:port/database`.

### How do I upgrade to a new version of TaxHacker?

Pull the latest image (`docker pull ghcr.io/vas3k/taxhacker:latest`) or rebuild your custom image from the updated repository. Stop the containers, run `docker compose up -d` to restart, and the [`docker-entrypoint.sh`](https://github.com/vas3k/TaxHacker/blob/main/docker-entrypoint.sh) script will automatically apply any pending database migrations via Prisma before starting the application.

### Is a GPU required for the AI document extraction features?

No. TaxHacker uses cloud-based AI providers (OpenAI, Google Gemini, or Mistral) via API calls defined in [`lib/config.ts`](https://github.com/vas3k/TaxHacker/blob/main/lib/config.ts). The server itself runs on standard CPU resources, though you must provide valid API keys for the respective providers to enable automatic receipt and invoice parsing.