# Deployment ## Install or update On a server with Docker Engine and Compose v2, clone/pull the repository, copy `.env.example` to `.env`, set `SITE_URL`, and run: ```sh docker compose up --build -d ``` Create the owner separately when needed: ```sh docker compose exec portfolio-app node maintenance.mjs create-user ``` `create-owner` and `reset-password` remain available. Startup never provisions an account. See [SELF_HOSTING.md](SELF_HOSTING.md) for private imports, persistent volumes, optional Caddy integration, backups, and the one-time upgrade steps. ## Gitea Actions Keep the existing trusted `portfolio-ci` runner with Docker/Compose, Bash, Git, and OpenSSH. No dedicated Buildx builder, shared image daemon, release manifests, or release-directory symlinks are required. The deployment server needs Git, Bash, `flock`, Docker/Compose, access to pull this repository, and a checkout on `main` at `DEPLOY_PATH`. Configure its `.env` once, just as for manual deployment. Keep these repository Actions secrets: | Secret | Value | | --- | --- | | `DEPLOY_HOST` | Server hostname/IP | | `DEPLOY_USER` | SSH user allowed to run Docker | | `DEPLOY_PORT` | Optional SSH port, default 22 | | `DEPLOY_PATH` | Absolute server checkout directory | | `DEPLOY_SSH_KEY` | Deployment SSH private key | | `DEPLOY_KNOWN_HOSTS` | Verified server SSH host-key entry | The server's checkout needs its own Git read access; the deployment SSH key is only for reaching the server. Protect the trusted runner from untrusted fork code. Fork pull requests do not run this workflow. - **Pull request or manual workflow:** build the production image, including TypeScript validation. No browser tests, unit suites, lint, or audit jobs. - **Push to main:** SSH to the server, serialize deployments, fast-forward to the triggering commit, back up an existing running installation, then run `docker compose up --build -d --remove-orphans --wait --wait-timeout 60`. An obsolete queued commit is skipped. The image is tagged with its commit SHA. Each deployment builds once on the server, where Docker retains its dependency, native-library, and Next.js caches. `NPM_REGISTRY` in the server `.env` selects a mirror if needed. The optional Gitea variable of the same name configures PR builds. Avoid pruning build caches between deployments. ## Timing and recovery The target is under 180 seconds for cached UI-change deployments. Cold builds, network transfers, native-library upgrades, and large backups may take longer. This change does not benchmark or enforce a wall-clock guarantee. Job timeouts allow a first build to finish rather than killing it at the routine-update target. Backups are retained under `backups/before--` in the server checkout and are ignored by Git. They contain the database, uploads, and generated secret; preserve an externally configured secret separately. Copy backups to separate storage through your normal backup process. If startup fails after a schema migration, do not simply downgrade the image. Restore the matching pre-upgrade backup and older image using the recovery instructions in SELF_HOSTING.md. A build failure leaves the running container intact; the checkout remains at the attempted commit so the deployment can be retried.