A registry stores Docker images. Your Mac builds the application and uploads images there; the VPS downloads and runs them. Git stores source code, while a registry stores the built application. They can use different providers.
This guide covers manual release to one VPS using Docker Hub, local working images, and disabled backups.
1. Create image repositories
Create three repositories in your registry: backend, admin, and ops. Ops is the deployment command service image. Example Docker Hub addresses:
docker.io/your-namespace/example-backend
docker.io/your-namespace/example-admin
docker.io/your-namespace/example-ops
Replace your-namespace with your Docker Hub username or organization. It is not a server IP, application domain, or Git project name. Choose your own three repository names; all must use the same registry host.
Prepare a token with upload permissions for the Mac in registry settings. A token is an access secret used instead of a password for Docker login. Private repositories also need a separate long-lived read token for the VPS. It must remain valid after build or CI job completion.
2. Create a profile in Generator UI
Enable Docker and Production deployment in Deployment, and choose CI provider → None. In Deployment environments, create profile production with Single server preset. Select Image delivery → Pull from registry, and enter your domains, TLS contact email, and Runtime directory. Choose None for Backup destination; working images use the project's Local files setting.
For field details, see profile preparation. Click Save deployment settings, then Generate. Settings appear in deploy/generated/environments/production.json; leave the empty adjacent custom file in deploy/custom/environments/ unchanged for now. Do not copy JSON into deploy/environments/.
Choose Registry provider in the profile and fill backend/admin/ops image repository using step 1 addresses. Commit generated files: registry-build requires clean tracked backend, admin, and deploy source.
3. Prepare VPS and SSH
Complete server preparation, then configure SSH on your computer through environment preparation. Fill Backend SSH target and this VPS's Runtime directory in the profile. All three image addresses are set in UI and need not be repeated in JSON.
For two VPS machines, use Separate frontend / backend preset, fill both SSH targets, and prepare the same Runtime directory on both. Point admin DNS at frontend and API at backend. Subsequent commands are unchanged.
4. Allow Mac and VPS to download images
On the Mac, sign in to the registry. For Docker Hub:
docker login docker.io --username YOUR_DOCKERHUB_LOGIN
Replace YOUR_DOCKERHUB_LOGIN with your Docker Hub user's login. For organization-owned repositories, their namespace in image addresses may differ from this login. At the password prompt, enter an upload token. Change host and login for another registry.
For private images, prepare a VPS access file on the Mac outside Git:
mkdir -p "$HOME/.config/admingen/production"
chmod 700 "$HOME/.config/admingen/production"
umask 077
export DEPLOY_SECRETS_FILE="$HOME/.config/admingen/production/secrets.json"
nano "$DEPLOY_SECRETS_FILE"
Enter JSON, replacing both examples with real values:
{
"registry_username": "YOUR_REGISTRY_USER",
"registry_password": "YOUR_REGISTRY_READ_TOKEN"
}
registry_username is the token owner's name according to registry rules; registry_password is the read token. Save the file and run:
chmod 600 "$DEPLOY_SECRETS_FILE"
Provision sends these credentials to the server. Public images without authentication need no server secrets file. PostgreSQL and authentication keys are created automatically in either case.
5. Perform first startup
On the Mac, from the example-project root, with Docker Desktop running and clean Git source. Export paths in a new terminal:
export DEPLOY_SSH_KEY_FILE="$HOME/.config/admingen/production/id_ed25519"
export DEPLOY_KNOWN_HOSTS_FILE="$HOME/.config/admingen/production/known_hosts"
If you prepared a private registry file, also export DEPLOY_SECRETS_FILE from the previous step in this terminal.
bash ./deploy/control build --env production --output release-001.json
bash ./deploy/control provision --env production --release release-001.json --confirm production
bash ./deploy/control deploy --env production --release release-001.json --confirm production
bash ./deploy/control status --env production --json
Run commands individually, proceeding only after success. Build builds the version; provision checks the environment and prepares secrets, PostgreSQL, and HTTPS; deploy applies migrations and starts the application. After provision, a maintenance page may still appear. Successful deploy ends with status=complete; status must show a recorded current release and successful readiness.
Build uploads three images to the registry and records their exact identifiers in JSON. The VPS downloads these versions without compiling source. Preserve needed release images in the registry: JSON alone cannot restore them.
Open a separate backend VPS terminal through configured SSH:
ssh -i "$DEPLOY_SSH_KEY_FILE" -o IdentitiesOnly=yes \
-o "UserKnownHostsFile=$DEPLOY_KNOWN_HOSTS_FILE" deploy@203.0.113.10
Create the first administrator
For a new project without data, run on the VPS as deploy or root:
docker ps --filter label=com.docker.compose.service=backend
Take the name from NAMES and replace ИМЯ_BACKEND_КОНТЕЙНЕРА:
docker exec -it ИМЯ_BACKEND_КОНТЕЙНЕРА \
admin bootstrap-superadmin --if-needed --login owner
Enter and confirm a password of 12–128 UTF-8 bytes; input is hidden. Open https://admin.example.com with your domain and sign in as owner. If a superadministrator exists, this does not replace it or reset its password.
To transfer a populated development database, after successful first deploy follow data transfer into a Docker environment. Import replaces the new database, including the created account; afterward use accounts from the transferred database.
Update the application
Back on the Mac, from the example-project root, prepare and commit code. Run git pull --ff-only for remote repository changes. Check that Docker login remains valid and both SSH paths are set in the terminal. Then:
bash ./deploy/control build --env production --output release-002.json
bash ./deploy/control deploy --env production --release release-002.json --confirm production
bash ./deploy/control status --env production --json
Use a new JSON name for each release. Provision is not repeated; ordinary deploy uses saved server credentials. Success means status=complete and a new current release. If the server token expires, update it through secret rotation.
Backup is disabled here. Backups, S3 media, and CI can be configured separately.
If release stops
From the same place where you ran control:
bash ./deploy/control status --env production --json
bash ./deploy/control logs --env production --service backend
Preserve the error message and operation ID. Continue with operation recovery. Do not delete data or state to retry. First deploy failure after migrations also requires operation investigation, even without a recorded current release.