Repository navigation
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
1014 lines (967 loc) · 37 KB
/
Copy pathdocker-compose.yml
File metadata and controls
1014 lines (967 loc) · 37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Host ports: every published port is 1024 or above. A port that Bitkit or the Pubky SDK dials by a fixed number keeps it
# (bitcoind 43782 and 39388, electrs 60001, homegate 6288, the Pubky testnet's 6881, 15411, 15412, 6286 and 6287, the
# Trezor Bridge's 21325 and 21328, Trezor User Env's controller 9001). Every other service publishes in 23000-23999,
# mostly at 23000 plus the last three digits of its container port. Services reach each other on their container
# ports. README "Ports" lists the map.
x-trezor-user-env-image: &trezor_user_env_image ghcr.io/trezor/trezor-user-env@sha256:15871ebb234bf7c0197cfcd31ddc2edbe90173cd6386fc0283fafe05bf888c06
services:
bitcoind:
container_name: bitcoin
image: btcpayserver/bitcoin:26.0
restart: unless-stopped
expose:
- '43782'
- '39388'
ports:
- '43782:43782'
- '39388:39388'
volumes:
- 'bitcoin_home:/home/bitcoin/.bitcoin'
environment:
BITCOIN_NETWORK: ${NBITCOIN_NETWORK:-regtest}
CREATE_WALLET: 'true'
BITCOIN_WALLETDIR: '/walletdata'
BITCOIN_EXTRA_ARGS: |
rpcport=43782
rpcbind=0.0.0.0:43782
rpcallowip=0.0.0.0/0
port=39388
whitelist=0.0.0.0/0
maxmempool=500
rpcauth=polaruser:5e5e98c21f5c814568f8b55d83b23c1c$$066b03f92df30b11de8e4b1b1cd5b1b4281aa25205bd57df9be82caf97a05526
txindex=1
fallbackfee=0.00002 # matches ldk-node fallback rate for ChannelCloseMinimum = 500 sat/kwu = 2 sat/vByte
zmqpubrawblock=tcp://0.0.0.0:28334
zmqpubrawtx=tcp://0.0.0.0:28335
zmqpubhashblock=tcp://0.0.0.0:28336
bitcoinsetup:
image: btcpayserver/bitcoin:26.0
depends_on:
- bitcoind
restart: 'no'
volumes:
- 'bitcoin_home:/home/bitcoin/.bitcoin'
user: bitcoin
# generate one block so electrs stop complaining
entrypoint:
[
'bash',
'-c',
'sleep 1; while ! bitcoin-cli -rpcconnect=bitcoind -generate 1; do sleep 1; done',
]
electrs:
container_name: electrum
image: getumbrel/electrs:v0.10.2
restart: unless-stopped
depends_on:
- bitcoind
expose:
- '60001'
- '28334'
- '28335'
- '28336'
ports:
- '60001:60001'
volumes:
- './electrs.toml:/data/electrs.toml'
environment:
- ELECTRS_NETWORK=regtest
- ELECTRS_ELECTRUM_RPC_ADDR=electrs:60001
- ELECTRS_DAEMON_RPC_ADDR=bitcoind:43782
- ELECTRS_DAEMON_P2P_ADDR=bitcoind:39388
- ELECTRS_LOG_FILTERS=INFO
darkhttpd:
image: p3terx/darkhttpd:1.16
restart: unless-stopped
depends_on:
- bitcoinsetup
expose:
- '80'
ports:
- '23080:80'
volumes:
- './btc-fee-estimates.json:/www/btc-fee-estimates.json'
command:
- '/www'
lnd:
container_name: lnd
image: polarlightning/lnd:0.18.0-beta
restart: unless-stopped
depends_on:
- bitcoind
expose:
- '8080' # REST
- '9735' # P2P
- '10009' # RPC
ports:
- '23180:8080' # REST
- '23735:9735' # P2P
- '23009:10009' # RPC
volumes:
- './lnd:/home/lnd/.lnd/'
command:
- '--noseedbackup'
- '--alias=lnd'
- '--externalip=127.0.0.1:23735' # the host port, so the node URI a wallet reads dials the published P2P port
- '--bitcoin.active'
- '--bitcoin.regtest'
- '--bitcoin.node=bitcoind'
- '--bitcoind.rpchost=bitcoind:43782'
- '--bitcoind.rpcuser=polaruser'
- '--bitcoind.rpcpass=polarpass'
- '--bitcoind.zmqpubrawblock=tcp://bitcoind:28334'
- '--bitcoind.zmqpubrawtx=tcp://bitcoind:28335'
- '--debuglevel=info'
- '--listen=0.0.0.0:9735'
- '--rpclisten=0.0.0.0:10009'
- '--restlisten=0.0.0.0:8080'
- '--feeurl=http://darkhttpd:80/btc-fee-estimates.json'
- '--protocol.option-scid-alias'
- '--protocol.zero-conf'
ldk-backup-server:
container_name: ldk-backup-server
image: synonymsoft/ldk-backup-server:0.0.146
expose:
- '3003'
ports:
- '23003:3003'
environment:
HOST: '0.0.0.0'
PORT: '3003'
SECRET_KEY: 'e3e2d2e410569be1f2219e1ef21f188f7ccff1ce721ea2624263d3fe9878f69e'
PUBLIC_KEY: '0319c4ff23820afec0c79ce3a42031d7fef1dff78b7bdd69b5560684f3e1827675'
lnurl-server:
build: ./lnurl-server
container_name: lnurl-server
environment:
- NODE_ENV=production
- PORT=3000
- DOMAIN=http://localhost:23000
- DATABASE_URL=sqlite:///data/lnurl.db
# Bitcoin node connection
- BITCOIN_RPC_HOST=bitcoind
- BITCOIN_RPC_PORT=43782
- BITCOIN_RPC_USER=polaruser
- BITCOIN_RPC_PASS=polarpass
# LND connection
- LND_REST_HOST=lnd
- LND_REST_PORT=8080
- LND_MACAROON_PATH=/lnd-certs/data/chain/bitcoin/regtest/admin.macaroon
- LND_TLS_CERT_PATH=/lnd-certs/tls.cert
volumes:
- ./lnurl-server/data:/data
- ./lnd:/lnd-certs:ro
ports:
- "23000:3000"
lnurl-server-fixture:
profiles: [lnurl-pay]
image: bitkit-docker/lnurl-server:pay-fixture
build:
context: ./lnurl-server
dockerfile: Dockerfile.pay-fixture
# real invoices of the project's LND (payable once `/channel/fixture` gave the wallet a channel), and `/fixture/mine` on its bitcoind
depends_on:
- lnd
- darkhttpd
environment:
PORT: '3010'
LNURL_FIXTURE_DOMAIN: ${LNURL_FIXTURE_DOMAIN:-}
LND_REST_URL: https://lnd:8080
LND_DIR: /lnd
LND_P2P_ADDRESS: 127.0.0.1:23735
BITCOIN_RPC_URL: http://polaruser:polarpass@bitcoind:43782
volumes:
- ./lnd:/lnd:ro
ports:
- '23010:3010'
healthcheck:
test: ['CMD', 'node', '-e', "fetch('http://127.0.0.1:3010/health').then(r => { if (!r.ok) process.exit(1); }).catch(() => process.exit(1))"]
interval: 5s
timeout: 3s
retries: 12
postgres:
container_name: postgres
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
- ./sql/v0_create_vss_db.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "23432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5
homegate-postgres:
container_name: homegate-postgres
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: pubky_homegate
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- homegate_postgres_data:/var/lib/postgresql/data
ports:
- "${HOMEGATE_POSTGRES_PORT:-23433}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d pubky_homegate"]
interval: 10s
timeout: 5s
retries: 5
homegate-admin-mock:
container_name: homegate-admin-mock
image: node:22-alpine
restart: unless-stopped
working_dir: /app
command: ["node", "server.js"]
environment:
HOMEGATE_ADMIN_MOCK_HOST: 0.0.0.0
HOMEGATE_ADMIN_MOCK_PORT: 6289
HOMEGATE_ADMIN_MOCK_PASSWORD: ${HOMEGATE_ADMIN_MOCK_PASSWORD:-admin}
HOMEGATE_ADMIN_MOCK_PUBKY: ${HOMEGATE_ADMIN_MOCK_PUBKY:-ufibwbmed6jeq9k4p583go95wofakh9fwpp4k734trq79pd9u1uy}
volumes:
- ./homegate-admin-mock:/app:ro
expose:
- "6289"
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:6289/info', { headers: { 'X-Admin-Password': process.env.HOMEGATE_ADMIN_MOCK_PASSWORD || 'admin' } }).then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
interval: 10s
timeout: 5s
retries: 5
lnurl-auth-server:
container_name: lnurl-auth-server
image: lnurl-auth-server:latest
build:
context: ../lnurl-auth-server
dockerfile: Dockerfile
restart: unless-stopped
environment:
- SERVER_HOST=0.0.0.0
- SERVER_PORT=5005
- SERVER_DOMAIN=${DOMAIN:-http://localhost:23005}
- JWT_PRIVATE_KEY_PATH=/app/keys/private.pem
- JWT_PUBLIC_KEY_PATH=/app/keys/public.pem
- JWT_EXPIRES_IN=24h
- CHALLENGE_EXPIRATION_MS=300000
- CHALLENGE_CLEANUP_INTERVAL_MS=60000
- CHALLENGE_LIMIT_MAX=10000
- LOG_LEVEL=info
- DB_PATH=/app/data/auth.db
- DB_MAX_MB=100
volumes:
- ./lnurl-server/keys:/app/keys:ro
- lnurl_auth_data:/app/data
ports:
- "23005:5005"
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "http://localhost:5005/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
vss-server:
container_name: vss-server
image: vss-server:latest
build:
context: ./vss-server
dockerfile: rust/Dockerfile
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
lnurl-auth-server:
condition: service_healthy
environment:
- VSS_SERVER_HOST=0.0.0.0
- VSS_SERVER_PORT=5050
- VSS_POSTGRESQL_HOST=postgres
- VSS_POSTGRESQL_PORT=5432
- VSS_POSTGRESQL_DATABASE=postgres
- VSS_POSTGRESQL_USERNAME=postgres
- VSS_POSTGRESQL_PASSWORD=postgres
- VSS_JWT_PUBLIC_KEY=-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UY2NeRbDpDHYVWWJPcx
dcnRD6KG205/jg8ei4FF4YtUKBvgEB0wuRkLdHkPfx4fXFRJELIFveYtWUOWwFMG
UDfMZDKwb6eMQw9bm2UjHc4CE6jOi1E5q93dPfD8Pyq6xdOY6/FkDX2Pa+Piz07b
7/1jWLbEtX02thPqcsNryBQC69QGhRHhXWt+uXhN72V/uTfoWObS8v7gLZuga3y+
dEKrtnmIYdW86XxBnWbeiujmqVCf2AnNiXgL+GwGOcksprYXmWXCvOojLK0C/H9d
eda1vOI7kpA+rseM6Egc43UsEZrZPmuO7C7l6brTl/ZJTAbwvBa7jPQ2ncH7cb86
TwIDAQAB
-----END PUBLIC KEY-----
volumes:
- ./vss-server-config.toml:/app/vss-server-config.toml:ro
ports:
- "23050:5050"
# Wallet-backup VSS for a host that already runs other services. `vss-server`
# above stays the default stack's service (it also starts lnurl-auth-server).
# This profile starts only the server and its database, on 127.0.0.1:23050,
# and checks RS256 JWTs with lnurl-server/keys/public.pem.
vss-postgres:
profiles: [vss]
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- vss_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 5s
timeout: 5s
retries: 20
vss:
profiles: [vss]
container_name: vss-server-fixture
image: vss-server:latest
build:
context: .
dockerfile: vss/Dockerfile
restart: unless-stopped
depends_on:
vss-postgres:
condition: service_healthy
environment:
VSS_SERVER_HOST: 0.0.0.0
VSS_SERVER_PORT: "5050"
VSS_POSTGRESQL_HOST: vss-postgres
VSS_POSTGRESQL_PORT: "5432"
VSS_POSTGRESQL_USERNAME: postgres
VSS_POSTGRESQL_PASSWORD: postgres
VSS_JWT_PUBLIC_KEY_FILE: /app/jwt-public.pem
volumes:
- ./vss-server-config.toml:/app/vss-server-config.toml:ro
- ./lnurl-server/keys/public.pem:/app/jwt-public.pem:ro
ports:
- "127.0.0.1:23050:5050"
homegate:
container_name: homegate
image: homegate:latest
build:
context: ./homegate
dockerfile: Dockerfile
restart: unless-stopped
depends_on:
homegate-postgres:
condition: service_healthy
homegate-admin-mock:
condition: service_healthy
volumes:
- homegate_data:/root/.homegate
- ./homegate-config.toml:/root/.homegate/config.toml:ro
ports:
- "${HOMEGATE_PORT:-6288}:6288"
# Disposable, automatically initialized emulator for isolated test projects.
# Its firmware and Bridge come from the same digest as the manual User Env.
trezor-emulator:
profiles: [trezor-emulator]
image: bitkit-docker/trezor-emulator:fixture-v1
build:
context: ./scripts
dockerfile: trezor-fixture.Dockerfile
restart: "no"
environment:
MACOS: "1" # use published ports rather than physical USB / host networking
TREZOR_MODEL: T2T1
TREZOR_FIRMWARE: 2-main # frozen in the pinned base image
TREZOR_BRIDGE_VERSION: node-bridge
TREZOR_MNEMONIC: all all all all all all all all all all all all
TREZOR_LABEL: Bitkit Test Trezor
TREZOR_WIPE: "true"
ports:
- "127.0.0.1:21325:21325" # Bridge legacy API used by Bitkit
- "127.0.0.1:21328:21328" # Bridge current API
- "127.0.0.1:23901:9001" # controller
- "127.0.0.1:23902:9002" # dashboard
- "127.0.0.1:23680:6080" # emulator screen / noVNC
volumes:
- trezor_emulator_state:/var/tmp
- trezor_emulator_logs:/trezor-user-env/logs
healthcheck:
test: ["CMD", "/trezor-user-env/.venv/bin/python3", "/opt/bitkit-trezor/trezor-fixture.py", "--health"]
interval: 5s
timeout: 20s
retries: 6
start_period: 120s
stop_grace_period: 20s
trezor-user-env-mac:
image: *trezor_user_env_image
ports:
- "9001:9001" # User Env controller websocket
- "23902:9002" # User Env dashboard
- "21325:21325" # Trezor Bridge legacy port used by Bitkit
- "21328:21328" # Trezor Bridge current port
- "23590:5900" # VNC
- "23680:6080" # noVNC web viewer
- "23903:9003" # MCP server SSE transport
environment:
- MACOS=1
- REGTEST_RPC_URL=http://host.docker.internal:43782
volumes:
- ./.trezor-user-env/trezor-suite:/trezor-user-env/trezor-suite
- ./.trezor-user-env/logs/screens:/trezor-user-env/logs/screens
- ./.trezor-user-env/logs/mcp-screenshots:/trezor-user-env/logs/mcp-screenshots
- ./.trezor-user-env/firmware/user_downloaded:/trezor-user-env/src/binaries/firmware/bin/user_downloaded
trezor-user-env-linux:
image: *trezor_user_env_image
profiles:
- trezor-linux
network_mode: "host"
environment:
- PHYSICAL_TREZOR=${PHYSICAL_TREZOR:-}
volumes:
- ./.trezor-user-env/trezor-suite:/trezor-user-env/trezor-suite
- ./.trezor-user-env/logs/screens:/trezor-user-env/logs/screens
- ./.trezor-user-env/logs/mcp-screenshots:/trezor-user-env/logs/mcp-screenshots
- ./.trezor-user-env/firmware/user_downloaded:/trezor-user-env/src/binaries/firmware/bin/user_downloaded
# ---------------------------------------------------------------------------
# Pubky marketplace fixture (profile "marketplace"). Not started by a plain
# `docker compose up`. Drive it with ./pubky-marketplace; see docs/pubky-marketplace.md.
# bitcoind and electrs above are the chain half of the fixture (Electrum on 60001).
# ---------------------------------------------------------------------------
marketplace-postgres:
profiles: [marketplace]
container_name: marketplace-postgres
image: postgres:16-alpine
restart: "no" # the fixture is disposable: a restarted testnet loses its accounts
environment:
POSTGRES_DB: paykit
POSTGRES_USER: marketplace
POSTGRES_PASSWORD: marketplace
volumes:
- marketplace_postgres_data:/var/lib/postgresql/data
- ./marketplace/postgres-init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U marketplace -d paykit"]
interval: 2s
timeout: 5s
retries: 30
# Pubky Core static testnet: DHT, PKARR relay, HTTP relay and one homeserver.
# The pinned Pubky clients (Bitkit E2E builds, Paykit Server) resolve this
# testnet on fixed localhost ports, so those ports are published unchanged on
# the host loopback and paykit-server and marketplace-driver join this
# container's network namespace.
pubky-testnet:
profiles: [marketplace]
container_name: marketplace-pubky-testnet
# The homeserver of 0.14.0 answers the Pubky SDK's LOCK and UNLOCK write locks that current Bitkit builds take before writing Paykit state; the
# earlier Pubky Core pin (f68014c1) answered 405.
image: bitkit-docker/pubky-testnet:0.14.0
build:
context: ./marketplace/pubky-testnet
restart: "no" # the fixture is disposable: a restarted testnet loses its accounts
depends_on:
marketplace-postgres:
condition: service_healthy
command: ["pubky-testnet", "--homeserver-config", "/etc/pubky-homeserver/config.toml"]
volumes:
- ./marketplace/homeserver.toml:/etc/pubky-homeserver/config.toml:ro
ports:
- "127.0.0.1:6881:6881/udp"
- "127.0.0.1:6881:6881/tcp"
- "127.0.0.1:15411:15411" # PKARR relay
- "127.0.0.1:15412:15412" # HTTP relay
- "127.0.0.1:6286:6286" # homeserver ICANN HTTP
- "127.0.0.1:6287:6297" # homeserver Pubky TLS, through homeserver-proxy (in-namespace clients reach the homeserver on 6287 directly)
- "127.0.0.1:23298:6298" # homeserver-proxy control: delay or fail one identity's requests
- "127.0.0.1:${MARKETPLACE_HOMESERVER_ADMIN_PORT:-23288}:6288" # homeserver admin (6288 is homegate's)
- "127.0.0.1:${MARKETPLACE_PAYKIT_PORT:-23101}:3001" # paykit-server, shares this namespace
- "127.0.0.1:23012:3012" # opt-in fixture-issuer, shares this namespace
- "127.0.0.1:23013:3013" # opt-in rc56-peer, shares this namespace
# Paykit Server 0ffd4da, the head of pubky/paykit-server#46 (paykit-rs rc65, the version both apps pin; Pubky 0.14.0; move to its merge or
# release once #46 lands), built from source with the upstream
# Dockerfile.local. Its setup flow emits the Pubky grant auth URL (cid and cpk)
# that the apps' Paykit SDK requires. `./pubky-marketplace build` first checks
# out the pinned trees under .marketplace/sources and confirms that the tree's
# Cargo.lock resolves paykit-rs and locks to them; Dockerfile.local then fails
# closed if a tree differs from the pins in Paykit Server's Cargo manifests.
paykit-server:
profiles: [marketplace]
container_name: marketplace-paykit-server
image: bitkit-docker/paykit-server:${PAYKIT_SERVER_TAG:-0ffd4da}
build:
context: ./.marketplace/sources/paykit-server
dockerfile: Dockerfile.local
labels:
tech.masivo.paykit-server: ${PAYKIT_SERVER_REV:-0ffd4da2adaab048939e0ea18ff916a27a7cfb0e}
tech.masivo.paykit-rs: ${PAYKIT_RS_REV:-7185ae7da9315028e5331442d71d739c27f1442c}
tech.masivo.paykit-server-patches: ${PAYKIT_SERVER_PATCHES:-paykit-server-reader-accepts-proposal-expiry.patch paykit-server-reader-accepts-request-without-reader-metadata.patch}
additional_contexts:
paykit-lib: ./.marketplace/sources/paykit-rs/paykit-lib
paykit-sdk: ./.marketplace/sources/paykit-rs/paykit-sdk
locks: ./.marketplace/sources/locks
restart: "no" # the fixture is disposable: a restarted testnet loses its accounts
network_mode: service:pubky-testnet
depends_on:
marketplace-postgres:
condition: service_healthy
electrs:
condition: service_started
pubky-testnet:
condition: service_started
environment:
PAYKIT_CONFIG: /state/paykit/paykit-server.toml
PAYKIT_DATABASE_URL: postgres://marketplace:marketplace@marketplace-postgres:5432/paykit
volumes:
- marketplace_state:/state:ro
# The master key and the generated config live in /state/paykit, written by
# `marketplace-driver init`; they never appear in compose or inspect output.
# The driver's seeds sit in /state/secrets (mode 0700, root), out of this
# container's reach.
# The driver writes the issuer's trust as `[locks] trusted_public_key`, which
# 0ffd4da requires; rc11 and later (`./pubky-marketplace build-paykit` with
# their pins, README "Pubky Marketplace Journey"; `follow-app-paykit` builds
# master when it locks the app's paykit-rs) refuse it and read
# `[signed_services] trusted_public_keys`. A server whose binary knows
# `signed_services` gets a copy in that form.
entrypoint:
- /bin/sh
- -c
- |
until [ -s /state/paykit/paykit-server.toml ] && [ -s /state/paykit/master-key ]; do
echo '[marketplace] waiting for marketplace-driver init'
sleep 1
done
PAYKIT_MASTER_KEY="$$(cat /state/paykit/master-key)"
export PAYKIT_MASTER_KEY
if grep -q signed_services /usr/local/bin/paykit-server && grep -q '^\[locks\]$$' "$$PAYKIT_CONFIG"; then
sed -e 's/^\[locks\]$$/[signed_services]/' -e 's/^trusted_public_key = \(".*"\)$$/trusted_public_keys = [\1]/' \
"$$PAYKIT_CONFIG" > /tmp/paykit-server.toml
PAYKIT_CONFIG=/tmp/paykit-server.toml
export PAYKIT_CONFIG
echo '[marketplace] Paykit Server reads [signed_services]: trust written in that form'
fi
exec /usr/local/bin/paykit-server
# The fixture driver. Runs one-shot commands (`docker compose run`) through
# the ./pubky-marketplace wrapper. It carries the paykit-companion-auth and
# paykit-reader-demo helper binaries from the paykit-server image.
marketplace-driver:
profiles: [marketplace]
image: bitkit-docker/marketplace-driver:${PAYKIT_SERVER_TAG:-0ffd4da}
build:
context: ./marketplace/driver
additional_contexts:
paykit: service:paykit-server
# the fixture on the server's paykit-rs (`./pubky-marketplace` sets the tag from PAYKIT_RS_TAG)
fixture: docker-image://bitkit-docker/payment-request-fixture:${PAYKIT_FIXTURE_TAG:-rc65-shared}
labels:
tech.masivo.paykit-server: ${PAYKIT_SERVER_REV:-0ffd4da2adaab048939e0ea18ff916a27a7cfb0e}
tech.masivo.paykit-rs: ${PAYKIT_RS_REV:-7185ae7da9315028e5331442d71d739c27f1442c}
network_mode: service:pubky-testnet
depends_on:
- pubky-testnet
environment:
BITCOIN_RPC_URL: http://bitcoind:43782
BITCOIN_RPC_USER: polaruser
BITCOIN_RPC_PASS: polarpass
PAYKIT_URL: http://127.0.0.1:3001
volumes:
- marketplace_state:/state
- ./.marketplace/evidence:/evidence
entrypoint: ["node", "/app/driver.mjs"]
# Pubky TLS proxy in front of the testnet homeserver: the apps reach the homeserver through it (the host's 6287 above).
# It presents the static testnet's homeserver key and forwards to the homeserver's plain HTTP on 6286; its control port
# (6298) delays or fails the requests of one identity and path (README, Homeserver proxy). Without rules it only forwards.
homeserver-proxy:
profiles: [marketplace]
image: bitkit-docker/payment-request-fixture:rc65-shared
build: ./payment-requests
restart: on-failure
network_mode: service:pubky-testnet
depends_on:
- pubky-testnet
entrypoint: ["/usr/local/bin/homeserver-proxy"]
# The rc65 SDK peers (paykit-rs 7185ae7, the version both apps pin) share the marketplace testnet namespace and regtest chain. The service name `rc56-peer` stays: lanes and journeys address the peer by it.
fixture-issuer:
profiles: [payment-requests]
image: bitkit-docker/payment-request-fixture:rc65-shared
build: ./payment-requests
restart: "no"
network_mode: service:pubky-testnet
depends_on:
- pubky-testnet
- bitcoind
environment:
FIXTURE_ROLE: fixture-issuer
FIXTURE_PORT: 3012
BITCOIN_RPC_URL: http://bitcoind:43782
rc56-peer:
profiles: [payment-requests]
image: bitkit-docker/payment-request-fixture:rc65-shared
build: ./payment-requests
restart: "no"
network_mode: service:pubky-testnet
depends_on:
- pubky-testnet
- bitcoind
environment:
FIXTURE_ROLE: rc56-peer
FIXTURE_PORT: 3013
BITCOIN_RPC_URL: http://bitcoind:43782
# ---------------------------------------------------------------------------
# Shop on staging with our own Paykit Server (profile "shop-mixed"). Paykit Server v0.1.0-rc11 (662dca06, paykit-rs ad3c7224 =
# v0.1.0-rc72, the version the Bitkit send-fix builds pin) resolves Pubky on mainnet, so it serves Synonym's staging homeserver,
# and watches Blocktank's staging regtest Electrum, the chain of Bitkit's staging builds. A Cloudflare quick tunnel gives it a
# public https URL. The driver plays the marketplace (Locks and the issuer); Bitkit wallets are the seller and the buyer.
# Drive it with ./shop-mixed; see docs/shop-mixed.md. Nothing of the local chain or Pubky testnet runs for it.
# ---------------------------------------------------------------------------
shop-mixed-postgres:
profiles: [shop-mixed, shop-order]
image: postgres:16-alpine
restart: "no"
environment:
POSTGRES_DB: paykit
POSTGRES_USER: paykit
POSTGRES_PASSWORD: paykit
volumes:
- shop_mixed_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paykit -d paykit"]
interval: 2s
timeout: 5s
retries: 30
shop-mixed-paykit:
profiles: [shop-mixed, shop-order]
image: bitkit-docker/paykit-server:${SHOP_MIXED_PAYKIT_SERVER_TAG:-v0.1.0-rc11}
build:
context: ./marketplace/shop-mixed
dockerfile: paykit-server.Dockerfile
args:
PAYKIT_SERVER_TAG: ${SHOP_MIXED_PAYKIT_SERVER_TAG:-v0.1.0-rc11}
PAYKIT_SERVER_REV: ${SHOP_MIXED_PAYKIT_SERVER_REV:-662dca0619a9aa2962bcd677bd5ddd4563cd2784}
PAYKIT_RS_REV: ${SHOP_MIXED_PAYKIT_RS_REV:-ad3c72248d18587bb5b6ef3c99b063fa9bf31551}
restart: "no"
depends_on:
shop-mixed-postgres:
condition: service_healthy
shop-mixed-driver:
condition: service_started
environment:
PAYKIT_CONFIG: /state/paykit/paykit-server.toml
PAYKIT_DATABASE_URL: postgres://paykit:paykit@shop-mixed-postgres:5432/paykit
volumes:
- shop_mixed_state:/state:ro
ports:
- "127.0.0.1:23110:3001"
# The config and master key are written by `shop-mixed-driver init`, which `up` runs first, as in the marketplace profile.
entrypoint:
- /bin/sh
- -c
- |
until [ -s /state/paykit/paykit-server.toml ] && [ -s /state/paykit/master-key ]; do
echo '[shop-mixed] waiting for shop-mixed-driver init'
sleep 1
done
PAYKIT_MASTER_KEY="$$(cat /state/paykit/master-key)"
export PAYKIT_MASTER_KEY
exec /usr/local/bin/paykit-server
# Cloudflare quick tunnel: https://<random words>.trycloudflare.com, a new name at every start, no account. Its metrics server
# answers /quicktunnel with the hostname (./shop-mixed url). Built from the pinned cloudflared image, so a project whose
# images are never pulled (the QA lanes) fetches it with `up --build`.
shop-mixed-tunnel:
profiles: [shop-mixed, shop-order]
image: bitkit-docker/shop-mixed-tunnel:2026.9.3
build:
context: ./marketplace/shop-mixed
dockerfile: tunnel.Dockerfile
restart: "no"
depends_on:
- shop-mixed-paykit
command: ["tunnel", "--metrics", "0.0.0.0:23111", "--url", "http://shop-mixed-paykit:3001"]
ports:
- "127.0.0.1:23111:23111"
# The driver's service writes the Paykit config, issuer and master keys (`init`) and stays up, so `up --wait` sees no exited
# one-shot container; ./shop-mixed runs the driver's other commands with `compose run`.
shop-mixed-driver:
profiles: [shop-mixed, shop-order]
image: bitkit-docker/shop-mixed-driver:local
build:
context: ./marketplace/driver
dockerfile: Dockerfile.staging
environment:
MARKETPLACE_BACKEND: staging
PAYKIT_URL: http://shop-mixed-paykit:3001
MARKETPLACE_TUNNEL_METRICS: http://shop-mixed-tunnel:23111
volumes:
- shop_mixed_state:/state
- ./.marketplace/evidence:/evidence
entrypoint: ["node", "/app/driver.mjs"]
command: ["init-and-stay"]
# ---------------------------------------------------------------------------
# Local Shop checkout with Synonym's staging homeserver (profile "shop-order", on top of shop-mixed): Paykit, Locks,
# the marketplace service, Shop and Postgres, with tunnels for the phone and seller browser. ./shop-order fetches
# the paired source revisions into .shop-order/src; docs/shop-order.md has the flow. Secrets are made once by
# shop-order-keys in the state volume, never committed.
# ---------------------------------------------------------------------------
shop-order-keys:
profiles: [shop-order]
image: node:22.11.0-alpine3.20
restart: "no"
environment:
SHOP_ORDER_FRESH: ${SHOP_ORDER_FRESH:-0}
volumes:
- shop_mixed_state:/state
- ./marketplace/shop-order:/order:ro
command: ["node", "/order/keys.mjs"]
shop-order-postgres:
profiles: [shop-order]
image: postgres:16-alpine
restart: "no"
environment:
POSTGRES_USER: shop
POSTGRES_DB: postgres
volumes:
- shop_order_postgres_data:/var/lib/postgresql/data
- shop_mixed_state:/state:ro
- ./marketplace/shop-order/postgres-init.sh:/docker-entrypoint-initdb.d/10-shop-order.sh:ro
entrypoint:
- /bin/sh
- -c
- |
set -a; . /state/order/postgres.env; set +a
exec docker-entrypoint.sh postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shop -d postgres"]
interval: 2s
timeout: 5s
retries: 30
shop-order-locks:
profiles: [shop-order]
image: bitkit-docker/locks-server:${SHOP_ORDER_LOCKS_REV:-be3f1b4d83b7318585d724fa6a429e1e5b3b0399}
build:
context: ./.shop-order/src/locks
restart: "no"
user: "0:0" # reads its secrets in the state volume (mode 0600)
depends_on:
shop-order-postgres:
condition: service_healthy
shop-mixed-paykit:
condition: service_started
volumes:
- shop_mixed_state:/state:ro
- ./marketplace/shop-order:/order:ro
- ./.shop-order/run:/run/order:ro
entrypoint: ["/bin/sh", "/order/start.sh", "locks"]
ports:
- "127.0.0.1:23120:3000"
shop-order-service:
profiles: [shop-order]
image: bitkit-docker/marketplace-service:${SHOP_ORDER_SERVICE_REV:-005b0707e6047b388ce032f4b51a2e0ed9e3a752}
build:
context: ./.shop-order/src/service
restart: "no"
user: "0:0" # reads its secrets in the state volume (mode 0600)
depends_on:
shop-order-postgres:
condition: service_healthy
volumes:
- shop_mixed_state:/state:ro
- ./marketplace/shop-order:/order:ro
- ./.shop-order/run:/run/order:ro
entrypoint: ["/bin/sh", "/order/start.sh", "service"]
ports:
- "127.0.0.1:23130:8080"
# The Shop BFF's own tables (db/bff in the Shop source), applied once to the bff database.
shop-order-bff-migrate:
profiles: [shop-order]
image: postgres:16-alpine
restart: "no"
depends_on:
shop-order-postgres:
condition: service_healthy
volumes:
- shop_mixed_state:/state
- ./.shop-order/src/shop/db/bff:/bff:ro
entrypoint:
- /bin/sh
- -c
- |
set -eu
[ -e /state/order/bff-migrated ] && { echo 'bff tables already applied'; exit 0; }
set -a; . /state/order/postgres.env; set +a
for file in /bff/*.sql; do
PGPASSWORD="$$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -h shop-order-postgres -U shop -d bff -f "$$file"
done
date -u > /state/order/bff-migrated
shop-order-shop:
profiles: [shop-order]
image: bitkit-docker/pubky-marketplace:${SHOP_ORDER_SHOP_REV:-af2866f58c8706e275a3028e41168c5976305808}
build:
context: ./.shop-order/src/shop
args:
NODE_MAX_OLD_SPACE_SIZE: ${SHOP_ORDER_NODE_HEAP_MB:-3072}
restart: "no"
user: "0:0" # reads the BFF secrets in the state volume (mode 0600)
depends_on:
shop-order-service:
condition: service_started
volumes:
- shop_mixed_state:/state:ro
- ./marketplace/shop-order:/order:ro
- ./.shop-order/run:/run/order:ro
entrypoint: ["/bin/sh", "/order/start.sh", "shop"]
ports:
- "127.0.0.1:23140:3000"
shop-order-locks-tunnel:
profiles: [shop-order]
image: bitkit-docker/shop-mixed-tunnel:2026.9.3
build:
context: ./marketplace/shop-mixed
dockerfile: tunnel.Dockerfile
restart: "no"
command: ["tunnel", "--metrics", "0.0.0.0:23121", "--url", "http://shop-order-locks:3000"]
ports:
- "127.0.0.1:23121:23121"
shop-order-service-tunnel:
profiles: [shop-order]
image: bitkit-docker/shop-mixed-tunnel:2026.9.3
build:
context: ./marketplace/shop-mixed
dockerfile: tunnel.Dockerfile
restart: "no"
command: ["tunnel", "--metrics", "0.0.0.0:23131", "--url", "http://shop-order-service:8080"]
ports:
- "127.0.0.1:23131:23131"
shop-order-shop-tunnel:
profiles: [shop-order]
image: bitkit-docker/shop-mixed-tunnel:2026.9.3
build:
context: ./marketplace/shop-mixed
dockerfile: tunnel.Dockerfile
restart: "no"
command: ["tunnel", "--metrics", "0.0.0.0:23141", "--url", "http://shop-order-shop:3000"]
ports:
- "127.0.0.1:23141:23141"
# USDT test chain and provider fixtures. Opt in with ./usdt-fixture.
# Shared loopback matches Core/gateway local endpoint validation.
usdt-fixture:
profiles: [usdt]
image: bitkit-docker/usdt-fixture:${USDT_IMAGE_TAG:-unbuilt}
build:
context: ./.usdt/build
dockerfile: usdt/Dockerfile
target: fixture
# File-backed Compose secrets retain host ownership on Linux.
user: "0:0"
init: true
restart: "no"
secrets:
- usdt_upstream
environment:
USDT_GATEWAY_PORT: ${USDT_GATEWAY_PORT:-23453}
USDT_CONTROL_PORT: ${USDT_CONTROL_PORT:-23454}
ports:
- "127.0.0.1:${USDT_GATEWAY_PORT:-23453}:23453"
- "127.0.0.1:${USDT_CONTROL_PORT:-23454}:23454"
healthcheck:
test: ["CMD", "node", "cli.cjs", "status"]
interval: 2s
timeout: 5s
retries: 60
start_period: 10s
usdt-node:
profiles: [usdt]
image: bitkit-docker/usdt-fixture:${USDT_IMAGE_TAG:-unbuilt}
init: true
restart: "no"
network_mode: service:usdt-fixture
depends_on:
usdt-fixture:
condition: service_started
entrypoint: [/bin/sh, -c]
volumes:
- usdt_fork_cache:/home/node/.foundry
command:
- node wait.cjs upstream && exec anvil --host 127.0.0.1 --port 23450 --fork-url http://127.0.0.1:23455 --fork-block-number "$$(node -p "require('./pins.json').forkBlock")" --chain-id 42161 --hardfork prague --silent --block-time 1 --compute-units-per-second 150 --fork-retry-backoff 1000 --no-fork-node-info
usdt-alto:
profiles: [usdt]
image: bitkit-docker/usdt-fixture:${USDT_IMAGE_TAG:-unbuilt}
init: true
restart: "no"
network_mode: service:usdt-fixture
depends_on:
usdt-fixture:
condition: service_healthy
entrypoint: [/bin/sh, -c]
command:
- |
KEY="$$(node -p "require('viem').toHex(require('./contracts.cjs').executor.getHdKey().privateKey)")"
exec node node_modules/@pimlico/alto/esm/cli/alto.js --entrypoints 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108 --rpc-url http://127.0.0.1:23450 --executor-private-keys "$$KEY" --utility-private-key "$$KEY" --port 23451 --chain-type default --safe-mode false --enable-debug-endpoints true --min-executor-balance 0 --log-level warn --json true
healthcheck:
test: ["CMD", "node", "-e", "require('./rpc.cjs').rpc('http://127.0.0.1:23451','eth_supportedEntryPoints').catch(()=>process.exit(1))"]
interval: 2s
timeout: 5s
retries: 30
usdt-gateway:
profiles: [usdt]
image: bitkit-docker/usdt-gateway:${USDT_IMAGE_TAG:-unbuilt}
build:
context: ./.usdt/build
dockerfile: usdt/Dockerfile
target: gateway
init: true
restart: "no"
network_mode: service:usdt-fixture
depends_on:
usdt-alto:
condition: service_healthy
environment:
NODE_ENV: test
HOST: 0.0.0.0
PORT: 23453
ARBITRUM_RPC_URL: http://127.0.0.1:23450
LOCAL_PROVIDER_URL: http://127.0.0.1:23452
LOCAL_ORCHESTRA_URL: http://127.0.0.1:23456
LOCAL_LAYERZERO_URL: http://127.0.0.1:23457
ORCHESTRA_API_KEY: local-orchestra-fixture
ORCHESTRA_BRIDGE_SECRET: abababababababababababababababababababababababababababababababab
ORCHESTRA_BRIDGE_NETWORKS: ethereum,polygon,base,bsc,tron,solana,plasma
ORCHESTRA_DEPOSIT_NETWORKS: ethereum,polygon,base,bsc,tron,solana
USDT_BRIDGE_NETWORKS: ethereum,polygon,plasma,stable
REQUESTS_PER_MINUTE: 10000
GLOBAL_REQUESTS_PER_MINUTE: 100000
PREPARATION_REQUESTS_PER_MINUTE: 10000
GLOBAL_PREPARATION_REQUESTS_PER_MINUTE: 100000
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:23453/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 2s
timeout: 5s
retries: 30
usdt-tools:
profiles: [usdt]
image: bitkit-docker/usdt-fixture:${USDT_IMAGE_TAG:-unbuilt}
network_mode: service:usdt-fixture
depends_on:
usdt-gateway:
condition: service_healthy
volumes:
- usdt_state:/state
- ./.usdt/evidence:/evidence:ro
entrypoint: ["node", "cli.cjs"]
volumes:
usdt_fork_cache:
usdt_state:
trezor_emulator_state:
trezor_emulator_logs:
bitcoin_home:
postgres_data:
vss_postgres_data: