diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..216f9ef --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,81 @@ +name: Docker image + +on: + push: + branches: [main] + release: + types: [published] + pull_request: + types: [opened, reopened, synchronize, labeled, unlabeled] + workflow_dispatch: + +permissions: + contents: read + packages: write + pull-requests: read + +concurrency: + group: docker-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + docker: + if: >- + ${{ github.event_name != 'pull_request' || + contains(github.event.pull_request.labels.*.name, 'build') || + github.event.label.name == 'build' }} + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Read application version + id: version + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + shell: bash + run: echo "value=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT" + + - name: Compute image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=${{ steps.version.outputs.value }},enable=${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + type=raw,value=latest,enable=${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + type=ref,event=pr + type=sha,prefix=sha- + + - name: Log in to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and publish (or validate PR build) + uses: docker/build-push-action@v6 + with: + context: . + 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/README.md b/README.md index 98d629a..72efdc9 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,14 @@ Set `BETTER_AUTH_SECRET` in `.env`. Either set `DATABASE_URL` (your Postgres; no --- +## Docker images + +Prebuilt multi-architecture images are published to `ghcr.io/zhravan/proxydeck` for `linux/amd64` and `linux/arm64`. + +See **[Docker image deployment](docs/docker.md)** for pull commands, a complete Docker Compose example with bundled PostgreSQL, version pinning, and CI/CD publishing behavior. + +--- +
Click to view .env variable definitions diff --git a/deploy/.env.ghcr.example b/deploy/.env.ghcr.example new file mode 100644 index 0000000..d5eed4e --- /dev/null +++ b/deploy/.env.ghcr.example @@ -0,0 +1,5 @@ +# Copy to .env and replace both placeholders before starting. +BETTER_AUTH_SECRET=replace-with-a-long-random-secret +BETTER_AUTH_URL=http://localhost:3000 +PORT=3000 +POSTGRES_PASSWORD=change-this-database-password diff --git a/deploy/docker-compose.ghcr.yml b/deploy/docker-compose.ghcr.yml new file mode 100644 index 0000000..839eb02 --- /dev/null +++ b/deploy/docker-compose.ghcr.yml @@ -0,0 +1,34 @@ +services: + postgres: + image: postgres:16-alpine + profiles: ["db"] + restart: unless-stopped + environment: + POSTGRES_USER: proxydeck + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} + POSTGRES_DB: proxydeck + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U proxydeck -d proxydeck"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 10s + app: + image: ghcr.io/zhravan/proxydeck:${PROXYDECK_VERSION:-latest} + pull_policy: always + restart: unless-stopped + ports: + - "${PORT:-3000}:3000" + environment: + PORT: 3000 + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?Set BETTER_AUTH_SECRET in .env} + BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000} + DATABASE_URL: ${DATABASE_URL:-postgres://proxydeck:${POSTGRES_PASSWORD}@postgres:5432/proxydeck} + depends_on: + postgres: + condition: service_healthy + required: false +volumes: + postgres_data: {} diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..42934ed --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,75 @@ +# Docker images + +ProxyDeck publishes multi-platform container images to GitHub Container Registry (GHCR). + +- Image: `ghcr.io/zhravan/proxydeck` +- Platforms: `linux/amd64` and `linux/arm64` +- Main-branch tags: the version in `package.json` and `latest`. +- Release tags: semantic version tags (`1.2.3`, `1.2`, `1`) from GitHub Releases. +- The image uses the repository `Dockerfile`; its entrypoint runs Drizzle migrations before starting the server. + +## Pull and run + +The GHCR package is intended to be public. Once the package has been made public, pull without authenticating: + +```bash +docker pull ghcr.io/zhravan/proxydeck:latest +``` + +A standalone container needs an existing PostgreSQL database reachable from the container: + +```bash +docker run -d --name proxydeck -p 3000:3000 \ + -e BETTER_AUTH_SECRET="$(openssl rand -base64 32)" \ + -e BETTER_AUTH_URL="http://localhost:3000" \ + -e DATABASE_URL="postgres://USER:PASSWORD@HOST:5432/proxydeck" \ + --restart unless-stopped \ + ghcr.io/zhravan/proxydeck:latest +``` + +## Complete setup with Docker Compose + +Download the checked-in Compose and environment examples: + +```bash +mkdir proxydeck && cd proxydeck +curl -fsSLO https://raw.githubusercontent.com/zhravan/proxydeck/main/deploy/docker-compose.ghcr.yml +curl -fsSLO https://raw.githubusercontent.com/zhravan/proxydeck/main/deploy/.env.ghcr.example +cp .env.ghcr.example .env +``` + +Generate secrets and replace the placeholders in `.env` (edit the file with your preferred editor): + +```bash +printf 'BETTER_AUTH_SECRET=%s\n' "$(openssl rand -base64 32)" +printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" +``` + +Put those generated values into `.env`, then start ProxyDeck: + +```bash +docker compose -f docker-compose.ghcr.yml --env-file .env --profile db up -d +docker compose -f docker-compose.ghcr.yml --env-file .env ps +``` + +Open [http://localhost:3000](http://localhost:3000). The app waits for PostgreSQL and automatically applies database migrations on startup. PostgreSQL data persists in the `postgres_data` named volume. + +Upgrade: + +```bash +docker compose -f docker-compose.ghcr.yml --env-file .env --profile db pull +docker compose -f docker-compose.ghcr.yml --env-file .env --profile db up -d +``` + +Pin a release by setting `PROXYDECK_VERSION=0.1.8` in `.env` (use any published version tag). For an external database, set `DATABASE_URL` in `.env` and omit `--profile db`. + +## CI and publishing + +The `.github/workflows/docker.yml` workflow: + +1. Builds and publishes `linux/amd64` and `linux/arm64` after every push/merge to `main`. +2. Tags main builds with the current `package.json` version and `latest`. +3. Publishes semantic version tags for published GitHub Releases. +4. Builds pull requests only after the `build` label is applied. Once labeled, every subsequent commit triggers another build. PR builds are validation-only and never push to GHCR. +5. Uses the built-in `GITHUB_TOKEN`; no PAT is required. Ensure **Settings → Actions → General → Workflow permissions** permits write access for packages. Set the GHCR package visibility to public after its first publication if anonymous pulls are desired. +