From a fresh Linux server to your first live visitor in about 15 minutes. Everything runs in Docker; you host it on your own infrastructure.
You need a Linux host (a small VM or on-prem server is fine) with:
Copy the solution folder to your server (e.g. via git clone or scp), then enter it:
# from your server
git clone <your-repo-url> visitor-management
cd visitor-management
Copy the template and open it in an editor:
cp .env.example .env nano .env
Set strong values for the items below. Generate the encryption key with the one-liner shown:
# passwords & secrets — use long random strings MARIADB_PASSWORD=... MARIADB_ROOT_PASSWORD=... MINIO_ACCESS_KEY=... MINIO_SECRET_KEY=... SECRET_KEY=... # field encryption key — generate this exact way: # python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" FIELD_ENCRYPTION_KEY=... # first admin account (created automatically on first start) BOOTSTRAP_ADMIN_EMAIL=admin@yourcompany.com BOOTSTRAP_ADMIN_PASSWORD=... # public URL used in gate-pass & feedback links PUBLIC_BASE_URL=https://vms.yourcompany.com
.env off version control. It already appears in .gitignore.docker compose up --build -d
This launches MariaDB, Redis, MinIO, the API, the background worker, the web app and the nginx proxy. On first start the backend automatically runs database migrations and creates your admin account and seed data.
docker compose ps curl http://localhost:8080/health
You should see all services running and a {"status":"ok"} response.
Sign in at /login with your BOOTSTRAP_ADMIN_* credentials, then open Admin and complete these one-time steps in order:
Go to Admin → Notifications. Choose your gateway and enter its credentials:
Credentials are encrypted at rest. Switching providers later is just a dropdown — no redeploy.
Under Admin → Team & Departments you can add people three ways:
.xlsx with headers:
full_name, email, mobile, role, designation, department_id, location_id, external_id.POST /api/admin/users/sync with a JSON array; records upsert by external_id.Assign a coordinator to each department — they receive department-level visits and diversions.
Under Admin → Categories, add visit categories (e.g. “Demo equipment / tools” — tick requires asset details) and purposes (e.g. “Business meeting”, “Interview”). These populate the visitor’s dropdowns.
Set the default visit validity window (DEFAULT_VISIT_VALIDITY_HOURS in .env) and decide whether returning visitors may skip the selfie (toggle in settings). Restart the stack after editing .env: docker compose up -d.
Open Admin → Reports for live insight: total visits, average engagement time, total team time spent with visitors, and the feedback rating distribution. A feedback link is sent to each visitor after checkout and its results roll straight into these reports.
Keep the in/out register live by having your badge/access-control system POST events to the app whenever someone badges in or out.
In Admin → Webhooks, create a config with a slug, a bearer secret, and the allowed source IPs of your access-control server. You’ll get a URL like https://vms.yourcompany.com/hooks/acs.
Configure it to send, on each badge event:
# POST https://vms.yourcompany.com/hooks/acs # Header: Authorization: Bearer <your-secret> { "employee_id": "E1234", "direction": "in" // or "out" }
The webhook page generates an nginx snippet for you. Paste it into your proxy so only the access-control server’s IPs can reach the endpoint:
location /hooks/acs {
allow 203.0.113.10; # your access-control server
deny all;
proxy_pass http://backend:8000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Every attempt — success or failure — is recorded. The last 1,000 attempts (time, source IP, result, message) are viewable on the same page for troubleshooting.
Under Admin → Branding, upload your company logo. The interface keeps its neutral slate base and automatically derives an accent colour from the logo, applied across the app. You can fine-tune the accent value manually if you prefer.
Downloadable image passes and calendar (.ics) files work out of the box on every device. To enable native Add to Apple Wallet on iPhone, supply your Apple signing assets in .env:
WALLET_PASS_TYPE_ID=pass.com.yourcompany.visitor WALLET_TEAM_ID=XXXXXXXXXX WALLET_CERT_PATH=/certs/pass.pem WALLET_KEY_PATH=/certs/pass.key WALLET_WWDR_PATH=/certs/wwdr.pem
Mount your certificate files into the backend container and restart. Until configured, iPhone users simply use Add to calendar and the downloadable pass.
PUBLIC_BASE_URL to your https:// domain.SECRET_KEY and provider credentials per your policy; never commit .env.SELFIE_RETENTION_DAYS to match your data-protection policy — old selfies are purged automatically.git pull && docker compose up --build -d — migrations run automatically on start.No. Everything runs in the browser — on a reception kiosk or the visitor’s own phone.
In on-prem MinIO object storage on your server, with an automatic retention limit. Nothing is sent to any cloud unless you choose a cloud SMS gateway.
Yes — use the Excel bulk upload for a one-off, or the REST sync endpoint for ongoing automation (upsert by external_id).
The routing engine falls back host → coordinator → reception, and the message that reaches reception explains that the team member and coordinator were both unavailable.
The selfie is captured and stored as the pass photo. Biometric face-matching is an optional future upgrade: the schema and a provider interface are already in place for an on-prem face service, disabled by default.