3.2 KiB
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:
docker compose up --build -d
Create the owner separately when needed:
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 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-<commit>-<timestamp> 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.